From: Jim Procter Date: Mon, 25 Jun 2018 10:48:11 +0000 (+0100) Subject: Merge branch 'Jalview-BH/JAL-3026' into tasks/JAL-3033_jalviewjs_ant X-Git-Tag: Develop-2_11_2_0-d20201215~24^2~68^2~629^2~19 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=298d676e11b2664a99a56b6c1d2a216e40727e9e;hp=f74c433b9f3ff2bd055e19f671b0568c97ba0c5b;p=jalview.git Merge branch 'Jalview-BH/JAL-3026' into tasks/JAL-3033_jalviewjs_ant --- diff --git a/.gitignore b/.gitignore index 2a55560..012a802 100644 --- a/.gitignore +++ b/.gitignore @@ -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 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 index 0000000..e69de29 diff --git a/buildcore.xml b/buildcore.xml new file mode 100644 index 0000000..f4dff04 --- /dev/null +++ b/buildcore.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + creating and compressing core files - warnings are OK; "does not exist" is trouble + reading core class list from file coreclasses + + + + + + + + + + ......Now copy an html file in site/ and add core:"core${core.name}", to the Info block. + + + + + + ......Creating core${call-core.name}.js + + + + + + + ......Generating ${site.path}/j2s/core/core${call-core.name}.js + + + ......Generating ${site.path}/j2s/core/core${call-core.name}.z.js + + + + + + + + + diff --git a/coreclasses b/coreclasses new file mode 100644 index 0000000..40ff83b --- /dev/null +++ b/coreclasses @@ -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 diff --git a/src/jalview/analysis/AlignmentSorter.java b/src/jalview/analysis/AlignmentSorter.java index b5cefe0..7ecce49 100755 --- a/src/jalview/analysis/AlignmentSorter.java +++ b/src/jalview/analysis/AlignmentSorter.java @@ -143,8 +143,8 @@ public class AlignmentSorter } // NOTE: DO NOT USE align.setSequenceAt() here - it will NOT work - List asq; - synchronized (asq = align.getSequences()) + List 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 algn; - synchronized (algn = align.getSequences()) + List algn = align.getSequences(); + synchronized (algn) { - List tmp = new ArrayList(); + List 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 groups = new ArrayList(); + List groups = new ArrayList<>(); if (groups.hashCode() != lastGroupHash) { @@ -315,7 +315,7 @@ public class AlignmentSorter // NOW ADD SEQUENCES MAINTAINING ALIGNMENT ORDER // ///////////////////////////////////////////// - List seqs = new ArrayList(); + List 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 seqs = new ArrayList(); + ArrayList seqs = new ArrayList<>(); int i, idx; boolean[] tmask = new boolean[mask.size()]; @@ -436,7 +436,7 @@ public class AlignmentSorter { int nSeq = align.getHeight(); - List tmp = new ArrayList(); + List tmp = new ArrayList<>(); tmp = _sortByTree(tree.getTopNode(), tmp, align.getSequences()); diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index d1217bf..14096cf 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -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++; diff --git a/src/jalview/analysis/Conservation.java b/src/jalview/analysis/Conservation.java index 131b39c..279d309 100755 --- a/src/jalview/analysis/Conservation.java +++ b/src/jalview/analysis/Conservation.java @@ -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 resultHash = new TreeMap(); + SortedMap 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(); + 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(); + 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))); } diff --git a/src/jalview/analysis/CrossRef.java b/src/jalview/analysis/CrossRef.java index e6bae9b..0e7662b 100644 --- a/src/jalview/analysis/CrossRef.java +++ b/src/jalview/analysis/CrossRef.java @@ -99,7 +99,7 @@ public class CrossRef */ public List findXrefSourcesForSequences(boolean dna) { - List sources = new ArrayList(); + List 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 foundSeqs = new ArrayList(); + List 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(); + 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 newDsSeqs = new ArrayList(), - doNotAdd = new ArrayList(); + List 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 ds; - synchronized (ds = dataset.getSequences()) + List ds = dataset.getSequences(); + synchronized (ds) { for (SequenceI nxt : ds) { diff --git a/src/jalview/appletgui/PaintRefresher.java b/src/jalview/appletgui/PaintRefresher.java index fe99187..ddf590e 100755 --- a/src/jalview/appletgui/PaintRefresher.java +++ b/src/jalview/appletgui/PaintRefresher.java @@ -52,7 +52,7 @@ public class PaintRefresher { if (components == null) { - components = new Hashtable>(); + 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 alsq; - synchronized (alsq = comp.getSequences()) + List alsq = comp.getSequences(); + synchronized (alsq) { alsq.add(i, a1[i]); } diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index dcd6546..d374682 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -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")); diff --git a/src/jalview/bin/Jalview.java b/src/jalview/bin/Jalview.java index 30620a1..0d7143d 100755 --- a/src/jalview/bin/Jalview.java +++ b/src/jalview/bin/Jalview.java @@ -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 index 0000000..84a17a4 --- /dev/null +++ b/src/jalview/bin/JalviewJS.java @@ -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 + * + *
+ *   JalviewJS file /examples/uniref50.fa features /examples/exampleFeatures.txt \
+ *       PDBFile "/examples/pdb1.txt seq1"
+ * 
+ * + * 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 params; + + private List 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 [features ]"); + 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 loadPdbParameters(ArgsParser parser) + { + List 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 index 0000000..037f41e --- /dev/null +++ b/src/jalview/bin/JalviewJS2.java @@ -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); + } + + +} diff --git a/src/jalview/commands/EditCommand.java b/src/jalview/commands/EditCommand.java index cac843f..b813dcf 100644 --- a/src/jalview/commands/EditCommand.java +++ b/src/jalview/commands/EditCommand.java @@ -114,7 +114,7 @@ public class EditCommand implements CommandI public abstract Action getUndoAction(); }; - private List edits = new ArrayList(); + private List 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 sequences; - synchronized (sequences = command.al.getSequences()) + List 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(); + command.deletedAnnotationRows = new Hashtable<>(); } if (command.fullAlignmentHeight) { @@ -948,7 +948,7 @@ public class EditCommand implements CommandI if (!insert) { - command.deletedAnnotations = new Hashtable(); + command.deletedAnnotations = new Hashtable<>(); } int aSize; @@ -1138,7 +1138,7 @@ public class EditCommand implements CommandI return; } - List oldsf = new ArrayList(); + List oldsf = new ArrayList<>(); int cSize = j - i; @@ -1196,7 +1196,7 @@ public class EditCommand implements CommandI if (command.editedFeatures == null) { - command.editedFeatures = new Hashtable>(); + command.editedFeatures = new Hashtable<>(); } command.editedFeatures.put(seq, oldsf); @@ -1233,7 +1233,7 @@ public class EditCommand implements CommandI */ public Map priorState(boolean forUndo) { - Map result = new HashMap(); + Map 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 editList = new ReverseListIterator(getEdits()); + Iterator editList = new ReverseListIterator<>(getEdits()); while (editList.hasNext()) { Edit oldEdit = editList.next(); @@ -1427,7 +1427,7 @@ public class EditCommand implements CommandI } else { - return new ReverseListIterator(getEdits()); + return new ReverseListIterator<>(getEdits()); } } } diff --git a/src/jalview/datamodel/HiddenSequences.java b/src/jalview/datamodel/HiddenSequences.java index c9dce08..b5efeb4 100755 --- a/src/jalview/datamodel/HiddenSequences.java +++ b/src/jalview/datamodel/HiddenSequences.java @@ -222,8 +222,8 @@ public class HiddenSequences end = hiddenSequences.length - 1; } - List asequences; - synchronized (asequences = alignment.getSequences()) + List asequences = alignment.getSequences(); + synchronized (asequences) { for (int index = end; index > start; index--) { diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index 9de9e3b..a2b1e47 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -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(); } diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java index 569257f..b4b0cdb 100644 --- a/src/jalview/gui/Desktop.java +++ b/src/jalview/gui/Desktop.java @@ -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; diff --git a/src/jalview/gui/OverviewCanvas.java b/src/jalview/gui/OverviewCanvas.java index cc361a5..ec2c49f 100644 --- a/src/jalview/gui/OverviewCanvas.java +++ b/src/jalview/gui/OverviewCanvas.java @@ -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); diff --git a/src/jalview/gui/PaintRefresher.java b/src/jalview/gui/PaintRefresher.java index ced5544..54fe488 100755 --- a/src/jalview/gui/PaintRefresher.java +++ b/src/jalview/gui/PaintRefresher.java @@ -39,7 +39,7 @@ import java.util.Map; */ public class PaintRefresher { - static Map> components = new HashMap>(); + static Map> components = new HashMap<>(); /** * Add the given component to those registered under the given sequence set @@ -60,7 +60,7 @@ public class PaintRefresher } else { - List vcoms = new ArrayList(); + List 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 alsq; - synchronized (alsq = comp.getSequences()) + List alsq = comp.getSequences(); + synchronized (alsq) { alsq.add(i, a1[i]); } @@ -240,7 +240,7 @@ public class PaintRefresher { return new AlignmentPanel[0]; } - List tmp = new ArrayList(); + List tmp = new ArrayList<>(); for (Component comp : comps) { if (comp instanceof AlignmentPanel) diff --git a/src/jalview/gui/SeqCanvas.java b/src/jalview/gui/SeqCanvas.java index 8f315bd..74af104 100755 --- a/src/jalview/gui/SeqCanvas.java +++ b/src/jalview/gui/SeqCanvas.java @@ -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"; diff --git a/src/jalview/gui/SequenceFetcher.java b/src/jalview/gui/SequenceFetcher.java index f545e70..0a51c7f 100755 --- a/src/jalview/gui/SequenceFetcher.java +++ b/src/jalview/gui/SequenceFetcher.java @@ -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 alsqs; - synchronized (alsqs = al.getSequences()) + List alsqs = al.getSequences(); + synchronized (alsqs) { for (SequenceI sq : alsqs) { diff --git a/src/jalview/io/AppletFormatAdapter.java b/src/jalview/io/AppletFormatAdapter.java index 5e209e6..3f51ea8 100755 --- a/src/jalview/io/AppletFormatAdapter.java +++ b/src/jalview/io/AppletFormatAdapter.java @@ -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; diff --git a/src/jalview/io/DataSourceType.java b/src/jalview/io/DataSourceType.java index 5d0c462..7e2aeab 100644 --- a/src/jalview/io/DataSourceType.java +++ b/src/jalview/io/DataSourceType.java @@ -22,5 +22,5 @@ package jalview.io; public enum DataSourceType { - FILE, URL, PASTE, CLASSLOADER; + FILE, URL, PASTE, CLASSLOADER, RELATIVE_URL; } diff --git a/src/jalview/io/FileLoader.java b/src/jalview/io/FileLoader.java index f26d6da..9dd740b 100755 --- a/src/jalview/io/FileLoader.java +++ b/src/jalview/io/FileLoader.java @@ -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 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()); } } diff --git a/src/jalview/io/FileParse.java b/src/jalview/io/FileParse.java index 7117d0f..bf0a844 100755 --- a/src/jalview/io/FileParse.java +++ b/src/jalview/io/FileParse.java @@ -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 diff --git a/src/jalview/io/IdentifyFile.java b/src/jalview/io/IdentifyFile.java index ff959b0..aafe934 100755 --- a/src/jalview/io/IdentifyFile.java +++ b/src/jalview/io/IdentifyFile.java @@ -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(); } diff --git a/src/jalview/io/vamsas/Tree.java b/src/jalview/io/vamsas/Tree.java index aa130cc..c0f65c4 100644 --- a/src/jalview/io/vamsas/Tree.java +++ b/src/jalview/io/vamsas/Tree.java @@ -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 findAlignmentSequences(AlignmentI jal, SeqCigar[] sequences) { SeqCigar[] tseqs = new SeqCigar[sequences.length]; System.arraycopy(sequences, 0, tseqs, 0, sequences.length); - Vector alsq = new Vector(); - List jalsqs; - synchronized (jalsqs = jal.getSequences()) + Vector alsq = new Vector<>(); + List 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 index 0000000..1b2b676 --- /dev/null +++ b/src/jalview/javascript/log4j/Appender.java @@ -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 index 0000000..f1aca45 --- /dev/null +++ b/src/jalview/javascript/log4j/ConsoleAppender.java @@ -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 index 0000000..ea79e1b --- /dev/null +++ b/src/jalview/javascript/log4j/Layout.java @@ -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 . + * 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 + * null. + */ + public String getHeader() + { + return null; + } + + /** + * Returns the footer for the layout format. The base class returns + * null. + */ + public String getFooter() + { + return null; + } + + /** + * If the layout handles the throwable object contained within + * {@link LoggingEvent}, then the layout should return false. + * Otherwise, if the layout ignores throwable object, then the layout should + * return true. If ignoresThrowable is true, the appender is + * responsible for rendering the throwable. + *

+ * The {@link SimpleLayout}, {@link TTCCLayout}, {@link PatternLayout} all + * return true. The {@link org.apache.log4j.xml.XMLLayout} + * returns false. + * + * @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 index 0000000..5691a47 --- /dev/null +++ b/src/jalview/javascript/log4j/Level.java @@ -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 + * OFF, FATAL, ERROR, WARN, + * INFO, DEBUG and ALL. + * + *

+ * The Level class may be subclassed to define a larger level set. + *

+ * + * @author Ceki Gülcü + */ +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 OFF 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 FATAL 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 ERROR 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 WARN level designates potentially harmful situations. + */ + final static public Level WARN = new Level(WARN_INT, WARN_NAME, 4); + + /** + * The INFO 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 DEBUG 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 TRACE Level designates finer-grained informational events + * than the DEBUGALL 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 defaultLevel. + */ + 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 index 0000000..bb7eb34 --- /dev/null +++ b/src/jalview/javascript/log4j/Logger.java @@ -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 . + * 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 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 index 0000000..2ace1eb --- /dev/null +++ b/src/jalview/javascript/log4j/Priority.java @@ -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 + +package jalview.javascript.log4j; + +/** + Refrain from using this class directly, use + the {@link Level} class instead. + + @author Ceki Gülcü */ +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 true if this level has a higher or equal + level than the level passed as argument, false + otherwise. + +

You should think twice before overriding the default + implementation of isGreaterOrEqual 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 index 0000000..9e5cbba --- /dev/null +++ b/src/jalview/javascript/log4j/SimpleLayout.java @@ -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 . + * 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 index 0000000..1a015f2 --- /dev/null +++ b/src/jalview/javascript/log4j/WriterAppender.java @@ -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 index 0000000..669ae52 --- /dev/null +++ b/src/jalview/javascript/log4j/spi/LoggingEvent.java @@ -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 . + * 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 index 0000000..9cd0f01 --- /dev/null +++ b/src/jalview/javascript/log4j/spi/OptionHandler.java @@ -0,0 +1,8 @@ +package jalview.javascript.log4j.spi; + +public interface OptionHandler +{ + + void activateOptions(); + +} diff --git a/src/jalview/util/BrowserLauncher.java b/src/jalview/util/BrowserLauncher.java index 0bc09cc..a55a65c 100755 --- a/src/jalview/util/BrowserLauncher.java +++ b/src/jalview/util/BrowserLauncher.java @@ -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. diff --git a/src/jalview/util/MessageManager.java b/src/jalview/util/MessageManager.java index 3494181..1cfe0c6 100644 --- a/src/jalview/util/MessageManager.java +++ b/src/jalview/util/MessageManager.java @@ -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; } diff --git a/src/jalview/util/Platform.java b/src/jalview/util/Platform.java index 2c74609..6a5f133 100644 --- a/src/jalview/util/Platform.java +++ b/src/jalview/util/Platform.java @@ -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(); } diff --git a/src/jalview/ws/rest/params/SeqGroupIndexVector.java b/src/jalview/ws/rest/params/SeqGroupIndexVector.java index dcb7fab..9210414 100644 --- a/src/jalview/ws/rest/params/SeqGroupIndexVector.java +++ b/src/jalview/ws/rest/params/SeqGroupIndexVector.java @@ -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 gl = new ArrayList(); + ArrayList gl = new ArrayList<>(); int p = 0, lowest = al.getHeight(), highest = 0; - List sgs; - synchronized (sgs = al.getGroups()) + List 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 getURLEncodedParameter() { - ArrayList prms = new ArrayList(); + ArrayList 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 index 0000000..b92c82b --- /dev/null +++ b/src/javajs/J2SIgnoreImport.java @@ -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 index 0000000..646cebd --- /dev/null +++ b/src/javajs/J2SRequireImport.java @@ -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 index 0000000..e8c3779 --- /dev/null +++ b/src/javajs/_README.txt @@ -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 index 0000000..0b0f29a --- /dev/null +++ b/src/javajs/api/BytePoster.java @@ -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 index 0000000..4a1b592 --- /dev/null +++ b/src/javajs/api/EigenInterface.java @@ -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 index 0000000..823bd2a --- /dev/null +++ b/src/javajs/api/GenericBinaryDocument.java @@ -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 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 index 0000000..aea6f63 --- /dev/null +++ b/src/javajs/api/GenericBinaryDocumentReader.java @@ -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 index 0000000..81a71f7 --- /dev/null +++ b/src/javajs/api/GenericCifDataParser.java @@ -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 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 index 0000000..57cf169 --- /dev/null +++ b/src/javajs/api/GenericColor.java @@ -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 index 0000000..54d8503 --- /dev/null +++ b/src/javajs/api/GenericImageDialog.java @@ -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 index 0000000..9aa7477 --- /dev/null +++ b/src/javajs/api/GenericImageEncoder.java @@ -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 params) throws Exception; + +} diff --git a/src/javajs/api/GenericLineReader.java b/src/javajs/api/GenericLineReader.java new file mode 100644 index 0000000..f113a8b --- /dev/null +++ b/src/javajs/api/GenericLineReader.java @@ -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 index 0000000..73649d7 --- /dev/null +++ b/src/javajs/api/GenericOutputChannel.java @@ -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 index 0000000..e2e8018 --- /dev/null +++ b/src/javajs/api/GenericZipInputStream.java @@ -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 index 0000000..47a2da2 --- /dev/null +++ b/src/javajs/api/GenericZipTools.java @@ -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 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 bdata, String name); + + public String cacheZipContents(BufferedInputStream bis, String shortName, + Map 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 index 0000000..418d528 --- /dev/null +++ b/src/javajs/api/Interface.java @@ -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 index 0000000..1c6c108 --- /dev/null +++ b/src/javajs/api/JSFunction.java @@ -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 index 0000000..0f785fa --- /dev/null +++ b/src/javajs/api/JSInterface.java @@ -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 index 0000000..b013279 --- /dev/null +++ b/src/javajs/api/JSONEncodable.java @@ -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 index 0000000..5ded55f --- /dev/null +++ b/src/javajs/api/ResettableStream.java @@ -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 index 0000000..a3310f6 --- /dev/null +++ b/src/javajs/api/ZInputStream.java @@ -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 index 0000000..687f642 --- /dev/null +++ b/src/javajs/api/js/J2SObjectInterface.java @@ -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 index 0000000..9b26631 --- /dev/null +++ b/src/javajs/api/js/JSInterface.java @@ -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 index 0000000..8306d94 --- /dev/null +++ b/src/javajs/export/PDFCreator.java @@ -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 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 Mapfonts; + + 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(); + indirectObjects = new Lst(); + //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 data) { + Hashtable info = new Hashtable(); + for (Entry 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 images; + + public void addImageResource(Object newImage, int width, int height, int[] buffer, boolean isRGB) { + PDFObject imageObj = newObject("XObject"); + if (images == null) + images = new Hashtable(); + 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 index 0000000..176dc19 --- /dev/null +++ b/src/javajs/export/PDFObject.java @@ -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 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(); + 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 d, String nl) { + SB sb = new SB(); + sb.append("<<"); + if (d.containsKey("Type")) + sb.append("/Type").appendO(d.get("Type")); + for (Entry 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) 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 createSubdict(Map d0, String dict) { + Map d = (Map) d0.get(dict); + if (d == null) + d0.put(dict, d = new Hashtable()); + return d; + } + + void addResource(String type, String key, String value) { + Map 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 index 0000000..4c94c9b --- /dev/null +++ b/src/javajs/img/BMPDecoder.java @@ -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 index 0000000..77ed11e --- /dev/null +++ b/src/javajs/img/CRCEncoder.java @@ -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 index 0000000..287f9c3 --- /dev/null +++ b/src/javajs/img/GifEncoder.java @@ -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 . 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. +//

+// Fetch the software.
+// Fetch the entire Acme package. +//

+// @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 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 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 { + + /** + * + */ + 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 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 tempColors = new Lst(); + Map ciHash = new Hashtable(); + 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 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 colorMap = new Hashtable(); + 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 quantizeColors(Lst tempColors) { + int n = tempColors.size(); + Lst cells = new Lst(); + 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 cells, + Map 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 nearestCell = new Hashtable(); + 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); + + // = 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 index 0000000..7781202 --- /dev/null +++ b/src/javajs/img/ImageEncoder.java @@ -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 . 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 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 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 index 0000000..796972f --- /dev/null +++ b/src/javajs/img/Jpg64Encoder.java @@ -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 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 index 0000000..4e49718 --- /dev/null +++ b/src/javajs/img/JpgEncoder.java @@ -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 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 index 0000000..7dfd4b8 --- /dev/null +++ b/src/javajs/img/PdfEncoder.java @@ -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 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 ht = new Hashtable(); + 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 index 0000000..2dc8485 --- /dev/null +++ b/src/javajs/img/PngEncoder.java @@ -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 - <0000000pt>+<000000len>" + * + * // tEXt chunk "Software - Jmol " + * + * // tEXt chunk "Creation Time - " + * + * // 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 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 index 0000000..4bdc980 --- /dev/null +++ b/src/javajs/img/PpmEncoder.java @@ -0,0 +1,60 @@ +// PpmEncoder - write out an image as a PPM +// +// Copyright (C)1996,1998 by Jef Poskanzer . 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 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 index 0000000..d20b6c2 --- /dev/null +++ b/src/javajs/util/A4.java @@ -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 index 0000000..f873116 --- /dev/null +++ b/src/javajs/util/AU.java @@ -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 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[] without warnings. + * + * @param Type of objects in the list. + * @param size Array size. + * @return Array of List + */ + @SuppressWarnings("unchecked") + public static Lst[] createArrayOfArrayList(int size) { + return new Lst[size]; + } + + /** + * Helper method for creating a Map[] without warnings. + * + * @param Type of object for the keys in the map. + * @param Type of object for the values in the map. + * @param size Array size. + * @return Array of Map + */ + @SuppressWarnings("unchecked") + public static Map[] 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 map, String root) { + Lst list = new Lst(); + 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 index 0000000..0b038c3 --- /dev/null +++ b/src/javajs/util/AjaxURLConnection.java @@ -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 index 0000000..3d6d4d7 --- /dev/null +++ b/src/javajs/util/AjaxURLStreamHandler.java @@ -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 index 0000000..ef903a2 --- /dev/null +++ b/src/javajs/util/AjaxURLStreamHandlerFactory.java @@ -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 htFactories = new Hashtable(); + + 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 index 0000000..737a6ab --- /dev/null +++ b/src/javajs/util/ArrayDataReader.java @@ -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 index 0000000..f09d272 --- /dev/null +++ b/src/javajs/util/BArray.java @@ -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 index 0000000..5722efd --- /dev/null +++ b/src/javajs/util/BC.java @@ -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 index 0000000..1f3aaa4 --- /dev/null +++ b/src/javajs/util/BS.java @@ -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. + * + *

+ * By default, all bits in the set initially have the value {@code + * false}. + * + *

+ * 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. + * + *

+ * Unless otherwise noted, passing a null parameter to any of the + * methods in a {@code BitSet} will result in a {@code + * NullPointerException}. + * + *

+ * 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. + * + *

+ * To iterate over the {@code true} bits in a {@code BitSet}, use the + * following loop: + * + *

+   * @code
+   * for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
+   *     // operate on index i here
+   * }}
+   * 
+ * + * @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 AND 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 OR 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 XOR 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: + *
    + *
  • The bit initially has the value {@code true}, and the corresponding bit + * in the argument has the value {@code false}. + *
  • The bit initially has the value {@code false}, and the corresponding + * bit in the argument has the value {@code true}. + *
+ * + * @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 BitSet. The algorithm + * used to compute it may be described as follows. + *

+ * Suppose the bits in the BitSet were to be stored in an array + * of long integers called, say, words, in such a + * manner that bit k is set in the BitSet (for + * nonnegative values of k) if and only if the expression + * + *

+   * ((k >> 6) < words.length) && ((words[k >> 6] & (1 << (bit & 0x3F))) != 0)
+   * 
+ * + * is true. Then the following definition of the hashCode method + * would be a correct implementation of the actual algorithm: + * + *
+   * public int hashCode() {
+   *  long h = 1234;
+   *  for (int i = words.length; --i >= 0;) {
+   *    h ˆ= words[i] * (i + 1);
+   *  }
+   *  return (int) ((h >> 32) ˆ h);
+   * }
+   * 
+ * + * Note that the hash code values change if the set of bits is altered. + *

+ * Overrides the hashCode method of Object. + * + * @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}, + * + *

+   * ((BitSet) obj).get(k) == this.get(k)
+   * 
+ * + * 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 ", " (a comma and + * a space) and surrounded by braces, resulting in the usual mathematical + * notation for a set of integers. + * + *

+ * Example: + * + *

+   * BitSet drPepper = new BitSet();
+   * 
+ * + * Now {@code drPepper.toString()} returns "{}". + *

+ * + *

+   * drPepper.set(2);
+   * 
+ * + * Now {@code drPepper.toString()} returns "{2}". + *

+ * + *

+   * drPepper.set(4);
+   * drPepper.set(10);
+   * 
+ * + * 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 index 0000000..a7ac3a9 --- /dev/null +++ b/src/javajs/util/Base64.java @@ -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 index 0000000..73ec57b --- /dev/null +++ b/src/javajs/util/BinaryDocument.java @@ -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 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 index 0000000..e46287e --- /dev/null +++ b/src/javajs/util/CU.java @@ -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 mapJavaScriptColors = new Hashtable(); + + 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 + *

+ * 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 index 0000000..51e969c --- /dev/null +++ b/src/javajs/util/CifDataParser.java @@ -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. +* +*

+* regarding the treatment of single quotes vs. primes in +* cif file, PMR wrote: +*

+*

+* * There is a formal grammar for CIF +* (see http://www.iucr.org/iucr-top/cif/index.html) +* which confirms this. The textual explanation is +*

+*

+* 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. +*

+*

+* 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 +* +* _example 'a dog's life' +* +* is legal; the data value is a dog's life. +*

+*

+* [PMR - the terminating character(s) are quote+whitespace. +* That would mean that: +* +* _example 'Jones' life' +* +* would be an error +*

+*

+* 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: +*

+*

+* 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. +*

+*/ + +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 htFields = new Hashtable(); + + //////////////////////////////////////////////////////////////// + // 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 getAllCifData() { + line = ""; + String key; + Map data = null, data0 = null; + Map allData = new Hashtable(); + Lst> models = new Lst>(); + allData.put("models", models); + asObject = (getVersion() >= 2); + nullString = null; + Lst> saveFrames = new Lst>(); + try { + while ((key = getNextToken()) != null) { + if (key.startsWith("global_") || key.startsWith("data_")) { + models.addLast(data0 = data = new Hashtable()); + 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 d = data; + data = new Hashtable(); + 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 data) throws Exception { + String key; + Lst keyWords = new Lst(); + 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()); + } + columnCount = keyWords.size(); + if (columnCount == 0) + return; + isLoop = true; + while (getData()) + for (int i = 0; i < columnCount; i++) + ((Lst)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 ";") + * 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 ; 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 bar (where 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 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 index 0000000..a57f6af --- /dev/null +++ b/src/javajs/util/CompoundDocDirEntry.java @@ -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 index 0000000..72317fe --- /dev/null +++ b/src/javajs/util/CompoundDocHeader.java @@ -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 index 0000000..2e2c242 --- /dev/null +++ b/src/javajs/util/CompoundDocument.java @@ -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 directory = new Lst(); + 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 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 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 index 0000000..b5c8717 --- /dev/null +++ b/src/javajs/util/DF.java @@ -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 index 0000000..1eb8ade --- /dev/null +++ b/src/javajs/util/DataReader.java @@ -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 index 0000000..ab22558 --- /dev/null +++ b/src/javajs/util/DebugJS.java @@ -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 index 0000000..52736c0 --- /dev/null +++ b/src/javajs/util/Eigen.java @@ -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. + * + *

+ * 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. + *

+ * 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 index 0000000..8d1a9a7 --- /dev/null +++ b/src/javajs/util/Encoding.java @@ -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 index 0000000..3bcb006 --- /dev/null +++ b/src/javajs/util/JSAudioThread.java @@ -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 index 0000000..552270d --- /dev/null +++ b/src/javajs/util/JSJSONParser.java @@ -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 parseMap(String str, boolean asHashTable) { + index = 0; + this.asHashTable = asHashTable; + this.str = str; + len = str.length(); + if (getChar() != '{') + return null; + returnChar(); + return (Map) getValue(false); + } + + /** + * Could return Integer, Float, Boolean, String, Map, Lst, 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 map = (asHashTable ? new Hashtable() : new HashMap()); + 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 l = new Lst(); + 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 index 0000000..58af722 --- /dev/null +++ b/src/javajs/util/JSONException.java @@ -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 index 0000000..be94c00 --- /dev/null +++ b/src/javajs/util/JSThread.java @@ -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 index 0000000..e1d973f --- /dev/null +++ b/src/javajs/util/LimitedLineReader.java @@ -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 index 0000000..7814977 --- /dev/null +++ b/src/javajs/util/ListDataReader.java @@ -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 arrays without any further adaptation. + * + */ + +public class ListDataReader extends DataReader { + private Lst data; + private int pt; + private int len; + + public ListDataReader() { + super(); + } + + @SuppressWarnings("unchecked") + @Override + public DataReader setData(Object data) { + this.data = (Lst) 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 index 0000000..e7fdda5 --- /dev/null +++ b/src/javajs/util/Lst.java @@ -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 + */ +@SuppressWarnings("serial") +public class Lst extends ArrayList { + + 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 index 0000000..88f6f4e --- /dev/null +++ b/src/javajs/util/M3.java @@ -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 index 0000000..260a82c --- /dev/null +++ b/src/javajs/util/M34.java @@ -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 index 0000000..58bd355 --- /dev/null +++ b/src/javajs/util/M4.java @@ -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 index 0000000..cea5a04 --- /dev/null +++ b/src/javajs/util/Matrix.java @@ -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. + *

+ * 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. + *

+ * 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 index 0000000..8b21cfe --- /dev/null +++ b/src/javajs/util/Measure.java @@ -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 transformPoints(Lst vPts, M4 m4, P3 center) { + Lst v = new Lst(); + 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 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; + } + Lstlist = new Lst(); + 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 ptsA, Lst 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 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 index 0000000..a0e6b8b --- /dev/null +++ b/src/javajs/util/MessagePackReader.java @@ -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 getMapForStream(BufferedInputStream is) throws Exception { + doc = new BinaryDocument().setStream(is, true); + Map map = readMap(); + is.close(); + return map; + } + + @SuppressWarnings("unchecked") + public Map readMap() throws Exception { + return (Map) 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 map = new Hashtable(); + 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 index 0000000..98151d1 --- /dev/null +++ b/src/javajs/util/OC.java @@ -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 index 0000000..1e1dfdb --- /dev/null +++ b/src/javajs/util/P3.java @@ -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 index 0000000..8f39550 --- /dev/null +++ b/src/javajs/util/P3i.java @@ -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 index 0000000..6478602 --- /dev/null +++ b/src/javajs/util/P4.java @@ -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 index 0000000..81653cf --- /dev/null +++ b/src/javajs/util/PT.java @@ -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) 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 h, String key) { + if ("this".equals(key)) + return h; + Object val = h.get(key); + if (val == null) + for (Entry 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 list, + Lst 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 list, + Lst 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 index 0000000..d4a7bf9 --- /dev/null +++ b/src/javajs/util/Quat.java @@ -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. + * + * + * + * 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. + + * + * + * 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 index 0000000..995a629 --- /dev/null +++ b/src/javajs/util/Rdr.java @@ -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 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 index 0000000..aed71f3 --- /dev/null +++ b/src/javajs/util/SB.java @@ -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 index 0000000..e78a6f8 --- /dev/null +++ b/src/javajs/util/StringDataReader.java @@ -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 index 0000000..6cd5108 --- /dev/null +++ b/src/javajs/util/T3.java @@ -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 index 0000000..08d02d7 --- /dev/null +++ b/src/javajs/util/T3d.java @@ -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 index 0000000..7fbd3bb --- /dev/null +++ b/src/javajs/util/T3i.java @@ -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 index 0000000..21b2741 --- /dev/null +++ b/src/javajs/util/T4.java @@ -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 index 0000000..51325d2 --- /dev/null +++ b/src/javajs/util/V3.java @@ -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 index 0000000..9060575 --- /dev/null +++ b/src/javajs/util/V3d.java @@ -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 index 0000000..3e30e4b --- /dev/null +++ b/src/javajs/util/XmlUtil.java @@ -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("\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("\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 + : "", "]]]]>") + "]]>"); + } + + /** + * standard data" + * + * @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 data" + * standard " + * + * @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); + } + + /** + * " + * + * 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("") ? +// PT.rep(s.substring(9, s.length()-3),"]]]]>", "]]>") : s); +// } +// + +} diff --git a/src/javajs/util/ZipData.java b/src/javajs/util/ZipData.java new file mode 100644 index 0000000..f20715f --- /dev/null +++ b/src/javajs/util/ZipData.java @@ -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 index 0000000..a4082ec --- /dev/null +++ b/src/javajs/util/ZipTools.java @@ -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 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 v = new Lst(); + 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 bdata, String name) { + readFileAsMapStatic(bis, bdata, name); + } + + public static void readFileAsMapStatic(BufferedInputStream bis, + Map 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 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 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 index 0000000..54227b8 --- /dev/null +++ b/src/org/apache/harmony/luni/util/ExternalMessages.properties @@ -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 index 0000000..cf6a86e --- /dev/null +++ b/src/org/apache/harmony/luni/util/ExternalMessages.properties.js @@ -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 index 0000000..d90ee77 --- /dev/null +++ b/src/org/apache/harmony/luni/util/Msg.java @@ -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. + *

+ * It is used by the system classes to provide national language support, by + * looking up messages in the + * org.apache.harmony.luni.util.ExternalMessages + * + * 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 KEY + * 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. + *

+ * 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] = ""; + 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(""); + 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 index 0000000..592d8b3 --- /dev/null +++ b/src/org/apache/harmony/luni/util/MsgHelp.java @@ -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. + *

+ * 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] = ""; + 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(""); + 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() { +// 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 index 0000000..3a511a7 --- /dev/null +++ b/src/org/apache/tools/bzip2/BZip2Constants.java @@ -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 + * 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. + *

+ * This interface is public for historical purposes. + * You should have no need to use it. + *

+ */ +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. + * + *

FIXME: This array should be in a private or package private + * location, since it could be modified by malicious code.

+ */ + 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 index 0000000..fc25a31 --- /dev/null +++ b/src/org/apache/tools/bzip2/CBZip2InputStream.java @@ -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 + * 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. + * + *

The decompression requires large amounts of memory. Thus you + * should call the {@link #close() close()} method as soon as + * possible, to force CBZip2InputStream to release the + * allocated memory. See {@link CBZip2OutputStream + * CBZip2OutputStream} for information about memory usage.

+ * + *

CBZip2InputStream 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.

+ * + *

Instances of this class are not threadsafe.

+ */ +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. + * + *

Although BZip2 headers are marked with the magic + * "Bz" 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.

+ * @param in + * + * @throws IOException + * if the stream content is malformed or an I/O error occurs. + * @throws NullPointerException + * if in == null + */ + public CBZip2InputStream(final InputStream in) throws IOException { + this(in, false); + } + + /** + * Constructs a new CBZip2InputStream which decompresses bytes + * read from the specified stream. + * + *

Although BZip2 headers are marked with the magic + * "Bz" 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.

+ * + * @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 in == null + */ + 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 index 0000000..23923a3 --- /dev/null +++ b/src/org/apache/tools/bzip2/CBZip2InputStreamFactory.java @@ -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 index 0000000..0102c8e --- /dev/null +++ b/src/org/apache/tools/bzip2/CRC.java @@ -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 + * 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 index 0000000..830961b --- /dev/null +++ b/src/org/json/simple/ItemList.java @@ -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 + */ +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 + */ +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 index 0000000..5e7452f --- /dev/null +++ b/src/org/json/simple/JSONAware.java @@ -0,0 +1,12 @@ +package org.json.simple; + +/** + * Beans that support customized output of JSON text shall implement this interface. + * @author FangYidong + */ +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 index 0000000..fafa36b --- /dev/null +++ b/src/org/json/simple/JSONObject.java @@ -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 + */ +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 index 0000000..ab63b3e --- /dev/null +++ b/src/org/json/simple/JSONStreamAware.java @@ -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 + */ +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 index 0000000..5da3cd0 --- /dev/null +++ b/src/org/json/simple/JSONValue.java @@ -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 + */ +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. + *

+ * 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. + *

+ * 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. + *

+ * If this object is a Map or a List, and it's also a JSONAware, JSONAware will be considered firstly. + *

+ * 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='\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 index 0000000..43b3a6c --- /dev/null +++ b/src/org/json/simple/README.txt @@ -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 index 0000000..0bb7baf --- /dev/null +++ b/src/org/json/simple/parser/ContainerFactory.java @@ -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 + */ +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 index 0000000..056a85c --- /dev/null +++ b/src/org/json/simple/parser/ContentHandler.java @@ -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 + */ +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 index 0000000..59f8e31 --- /dev/null +++ b/src/org/json/simple/parser/JSONParser.java @@ -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 + */ +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 index 0000000..09b1fd4 --- /dev/null +++ b/src/org/json/simple/parser/ParseException.java @@ -0,0 +1,90 @@ +package org.json.simple.parser; + +/** + * ParseException explains why and where the error occurs in source JSON text. + * + * @author FangYidong + * + */ +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 index 0000000..dc36fa2 --- /dev/null +++ b/src/org/json/simple/parser/Yylex.java @@ -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 aState + */ + 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 false, 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 + * cannot be reused (internal buffer is discarded and lost). + * Lexical state is set to ZZ_INITIAL. + * + * @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 pos 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 index 0000000..9d7e7e7 --- /dev/null +++ b/src/org/json/simple/parser/Yytoken.java @@ -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 + */ +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 index 0000000..9285eac --- /dev/null +++ b/src/org/xml/sax/AttributeList.java @@ -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. + * + *

+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

An attribute list includes only attributes that have been + * specified or defaulted: #IMPLIED attributes will not be included.

+ * + *

There are two ways for the SAX application to obtain information + * from the AttributeList. First, it can iterate through the entire + * list:

+ * + *
+ * 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);
+ *     [...]
+ *   }
+ * }
+ * 
+ * + *

(Note that the result of getLength() will be zero if there + * are no attributes.) + * + *

As an alternative, the application can request the value or + * type of specific attributes:

+ * + *
+ * public void startElement (String name, AttributeList atts) {
+ *   String identifier = atts.getValue("id");
+ *   String label = atts.getValue("label");
+ *   [...]
+ * }
+ * 
+ * + * @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 { + + + //////////////////////////////////////////////////////////////////// + // Iteration methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Return the number of attributes in this list. + * + *

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.

+ * + * @return The number of attributes in the list. + */ + public abstract int getLength (); + + + /** + * Return the name of an attribute in this list (by position). + * + *

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.

+ * + *

If the attribute name has a namespace prefix, the prefix + * will still be attached.

+ * + * @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). + * + *

The attribute type is one of the strings "CDATA", "ID", + * "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", + * or "NOTATION" (always in upper case).

+ * + *

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").

+ * + *

For an enumerated attribute that is not a notation, the + * parser will report the type as "NMTOKEN".

+ * + * @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). + * + *

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.

+ * + * @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); + + + + //////////////////////////////////////////////////////////////////// + // Lookup methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Return the type of an attribute in the list (by name). + * + *

The return value is the same as the return value for + * getType(int).

+ * + *

If the attribute name has a namespace prefix in the document, + * the application must include the prefix here.

+ * + * @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). + * + *

The return value is the same as the return value for + * getValue(int).

+ * + *

If the attribute name has a namespace prefix in the document, + * the application must include the prefix here.

+ * + * @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 index 0000000..b25432d --- /dev/null +++ b/src/org/xml/sax/Attributes.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

This interface allows access to a list of attributes in + * three different ways:

+ * + *
    + *
  1. by attribute index;
  2. + *
  3. by Namespace-qualified name; or
  4. + *
  5. by qualified (prefixed) name.
  6. + *
+ * + *

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 http://xml.org/sax/features/namespace-prefixes + * feature is set to true (it is false by + * default). + * Because SAX2 conforms to the original "Namespaces in XML" + * recommendation, it normally does not + * give namespace declaration attributes a namespace URI. + *

+ * + *

Some SAX2 parsers may support using an optional feature flag + * (http://xml.org/sax/features/xmlns-uris) 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.

+ * + *

If the namespace-prefixes feature (see above) is + * false, access by qualified name may not be available; if + * the http://xml.org/sax/features/namespaces feature is + * false, access by Namespace-qualified names may not be + * available.

+ * + *

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 getIndex methods (below).

+ * + *

The order of attributes in the list is unspecified, and will + * vary from implementation to implementation.

+ * + * @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 +{ + + + //////////////////////////////////////////////////////////////////// + // Indexed access. + //////////////////////////////////////////////////////////////////// + + + /** + * Return the number of attributes in the list. + * + *

Once you know the number of attributes, you can iterate + * through the list.

+ * + * @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. + * + *

The attribute type is one of the strings "CDATA", "ID", + * "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", + * or "NOTATION" (always in upper case).

+ * + *

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").

+ * + *

For an enumerated attribute that is not a notation, the + * parser will report the type as "NMTOKEN".

+ * + * @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. + * + *

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.

+ * + * @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); + + + + //////////////////////////////////////////////////////////////////// + // 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. + * + *

See {@link #getType(int) getType(int)} for a description + * of the possible types.

+ * + * @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. + * + *

See {@link #getType(int) getType(int)} for a description + * of the possible types.

+ * + * @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. + * + *

See {@link #getValue(int) getValue(int)} for a description + * of the possible values.

+ * + * @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. + * + *

See {@link #getValue(int) getValue(int)} for a description + * of the possible values.

+ * + * @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 index 0000000..3d95c5f --- /dev/null +++ b/src/org/xml/sax/ContentHandler.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

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).

+ * + *

Implementors should note that there is also a + * ContentHandler class in the java.net + * package; that means that it's probably a bad idea to do

+ * + *
import java.net.*;
+ * import org.xml.sax.*;
+ * 
+ * + *

In fact, "import ...*" is usually a sign of sloppy programming + * anyway, so the user should consider this a feature rather than a + * bug.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

The SAX parser will invoke this method only once, before any + * other event callbacks (except for {@link #setDocumentLocator + * setDocumentLocator}).

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + * @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. + * + *

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 http://xml.org/sax/features/namespaces + * feature is true (the default).

+ * + *

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.

+ * + *

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.

+ * + *

There should never be start/endPrefixMapping events for the + * "xml" prefix, since it is predeclared and immutable.

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

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.

+ * + *

This event allows up to three name components for each + * element:

+ * + *
    + *
  1. the Namespace URI;
  2. + *
  3. the local name; and
  4. + *
  5. the qualified (prefixed) name.
  6. + *
+ * + *

Any or all of these may be provided, depending on the + * values of the http://xml.org/sax/features/namespaces + * and the http://xml.org/sax/features/namespace-prefixes + * properties:

+ * + *
    + *
  • the Namespace URI and local name are required when + * the namespaces property is true (the default), and are + * optional when the namespaces property is false (if one is + * specified, both must be);
  • + *
  • the qualified name is required when the namespace-prefixes property + * is true, and is optional when the namespace-prefixes property + * is false (the default).
  • + *
+ * + *

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 + * http://xml.org/sax/features/namespace-prefixes + * property is true (it is false by default, and support for a + * true value is optional).

+ * + *

Like {@link #characters characters()}, attribute values may have + * characters that need more than one char value.

+ * + * @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. + * + *

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).

+ * + *

For information on the names, see startElement.

+ * + * @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. + * + *

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.

+ * + *

The application must not attempt to read from the array + * outside of the specified range.

+ * + *

Individual characters may consist of more than one Java + * char 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 Surrogate Pair, + * 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.

+ * + *

Your code should not assume that algorithms using + * char-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.

+ * + *

Note that some parsers will report whitespace in element + * content using the {@link #ignorableWhitespace ignorableWhitespace} + * method rather than this one (validating parsers must + * do so).

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

The application must not attempt to read from the array + * outside of the specified range.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

Like {@link #characters characters()}, processing instruction + * data may have characters that need more than one char + * value.

+ * + * @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.) + * + *

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 + * http://xml.org/sax/features/external-general-entities + * and the + * http://xml.org/sax/features/external-parameter-entities + * properties.

+ * + * @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 index 0000000..e62222a --- /dev/null +++ b/src/org/xml/sax/DTDHandler.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

Note that this interface includes only those DTD events that + * the XML recommendation requires processors to report: + * notation and unparsed entity declarations.

+ * + *

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.) + *

+ * + *

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.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

There is no guarantee that the notation declaration will be + * reported before any unparsed entities that use it.

+ * + * @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. + * + *

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. + *

+ * + *

If the system identifier is a URL, the parser must resolve it + * fully before passing it to the application.

+ * + * @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 index 0000000..d70ae74 --- /dev/null +++ b/src/org/xml/sax/DocumentHandler.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

The SAX parser will invoke this method only once, before any + * other methods in this interface or in DTDHandler (except for + * setDocumentLocator).

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + * @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. + * + *

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).

+ * + *

If the element name has a namespace prefix, the prefix will + * still be attached to the name.

+ * + * @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. + * + *

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.

+ * + *

The application must not attempt to read from the array + * outside of the specified range.

+ * + *

Note that some parsers will report whitespace using the + * ignorableWhitespace() method rather than this one (validating + * parsers must do so).

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

The application must not attempt to read from the array + * outside of the specified range.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + * @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 index 0000000..65b04eb --- /dev/null +++ b/src/org/xml/sax/EntityResolver.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + *

The following resolver would provide the application + * with a special character stream for the entity with the system + * identifier "http://www.myhost.com/today":

+ * + *
+ * 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;
+ *     }
+ *   }
+ * }
+ * 
+ * + *

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).

+ * + * @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. + * + *

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).

+ * + *

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.

+ * + *

If the system identifier is a URL, the SAX parser must + * resolve it fully before reporting it to the application.

+ * + * @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 index 0000000..37d2501 --- /dev/null +++ b/src/org/xml/sax/ErrorHandler.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

WARNING: If an application does not + * register an ErrorHandler, XML parsing errors will go unreported, + * except that SAXParseExceptions 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.

+ * + *

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.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

Filters may use this method to report other, non-XML warnings + * as well.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

Filters may use this method to report other, non-XML errors + * as well.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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 index 0000000..7c5c411 --- /dev/null +++ b/src/org/xml/sax/HandlerBase.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

Note that the use of this class is optional.

+ * + * @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 +{ + + + //////////////////////////////////////////////////////////////////// + // Default implementation of the EntityResolver interface. + //////////////////////////////////////////////////////////////////// + + /** + * Resolve an external entity. + * + *

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.

+ * + * @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; + } + + + + //////////////////////////////////////////////////////////////////// + // Default implementation of DTDHandler interface. + //////////////////////////////////////////////////////////////////// + + + /** + * Receive notification of a notation declaration. + * + *

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.

+ * + * @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. + * + *

By default, do nothing. Application writers may override this + * method in a subclass to keep track of the unparsed entities + * declared in a document.

+ * + * @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 + } + + + + //////////////////////////////////////////////////////////////////// + // Default implementation of DocumentHandler interface. + //////////////////////////////////////////////////////////////////// + + + /** + * Receive a Locator object for document events. + * + *

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.

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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.

+ * + * @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 + } + + + + //////////////////////////////////////////////////////////////////// + // Default implementation of the ErrorHandler interface. + //////////////////////////////////////////////////////////////////// + + + /** + * Receive notification of a parser warning. + * + *

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.

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

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.

+ * + * @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 index 0000000..663700c --- /dev/null +++ b/src/org/xml/sax/InputSource.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

Applications may use setPublicId to include a + * public identifier as well, or setEncoding to specify + * the character encoding, if known.

+ * + *

If the system identifier is a URL, it must be fully + * resolved (it may not be a relative URL).

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

Application writers should use setSystemId() to provide a base + * for resolving relative URIs, and may use setPublicId to include a + * public identifier.

+ * + *

The character stream shall not include a byte order mark.

+ * + * @see #setPublicId + * @see #setSystemId + * @see #setByteStream + * @see #setCharacterStream + */ + public InputSource (Reader characterStream) + { + setCharacterStream(characterStream); + } + + + /** + * Set the public identifier for this input source. + * + *

The public identifier is always optional: if the application + * writer includes one, it will be provided as part of the + * location information.

+ * + * @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. + * + *

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).

+ * + *

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.

+ * + *

If the system identifier is a URL, it must be fully + * resolved (it may not be a relative URL).

+ * + * @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. + * + *

The getEncoding method will return the character encoding + * of the object pointed to, or null if unknown.

+ * + *

If the system ID is a URL, it will be fully resolved.

+ * + * @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. + * + *

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.

+ * + *

If the application knows the character encoding of the + * byte stream, it should set it with the setEncoding method.

+ * + * @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. + * + *

The getEncoding method will return the character + * encoding for this byte stream, or null if unknown.

+ * + * @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. + * + *

The encoding must be a string acceptable for an + * XML encoding declaration (see section 4.3.3 of the XML 1.0 + * recommendation).

+ * + *

This method has no effect when the application provides a + * character stream.

+ * + * @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. + * + *

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.

+ * + * @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; + } + + + + //////////////////////////////////////////////////////////////////// + // 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 index 0000000..f8f3484 --- /dev/null +++ b/src/org/xml/sax/Locator.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

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.

+ * + *

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 file:... URL, and other + * kinds of relative URI are also resolved against their bases.

+ * + * @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. + * + *

Warning: 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.

+ * + *

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.

+ * + *

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.

+ * + * @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 char values since + * the last line end. + * + *

Warning: 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.

+ * + *

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.

+ * + *

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.

+ * + * @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 index 0000000..734819d --- /dev/null +++ b/src/org/xml/sax/Parser.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

All SAX1 parsers must also implement a zero-argument constructor + * (though other constructors are also allowed).

+ * + *

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.

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

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).

+ * + *

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.

+ * + * @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. + * + *

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).

+ * + *

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.

+ * + * @param handler The DTD handler. + * @see DTDHandler + * @see HandlerBase + */ + public abstract void setDTDHandler (DTDHandler handler); + + + /** + * Allow an application to register a document event handler. + * + *

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).

+ * + *

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.

+ * + * @param handler The document handler. + * @see DocumentHandler + * @see HandlerBase + */ + public abstract void setDocumentHandler (DocumentHandler handler); + + + /** + * Allow an application to register an error event handler. + * + *

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).

+ * + *

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.

+ * + * @param handler The error handler. + * @see ErrorHandler + * @see SAXException + * @see HandlerBase + */ + public abstract void setErrorHandler (ErrorHandler handler); + + + /** + * Parse an XML document. + * + *

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).

+ * + *

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.

+ * + * @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). + * + *

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:

+ * + *
+     * parse(new InputSource(systemId));
+     * 
+ * + *

If the system identifier is a URL, it must be fully resolved + * by the application before it is passed to the parser.

+ * + * @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 index 0000000..70a60ce --- /dev/null +++ b/src/org/xml/sax/SAXException.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

The existing exception will be embedded in the new + * one, and its message will become the default message for + * the SAXException.

+ * + * @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. + * + *

The existing exception will be embedded in the new + * one, but the new exception will have its own message.

+ * + * @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. + * + *

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.

+ * + * @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(); + } + } + + + + ////////////////////////////////////////////////////////////////////// + // 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 index 0000000..0bb1ded --- /dev/null +++ b/src/org/xml/sax/SAXNotRecognizedException.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + * @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 index 0000000..9a645a9 --- /dev/null +++ b/src/org/xml/sax/SAXNotSupportedException.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + * @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 index 0000000..1df5e14 --- /dev/null +++ b/src/org/xml/sax/SAXParseException.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

Since this exception is a subclass of {@link org.xml.sax.SAXException + * SAXException}, it inherits the ability to wrap another exception.

+ * + * @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 { + + + ////////////////////////////////////////////////////////////////////// + // Constructors. + ////////////////////////////////////////////////////////////////////// + + + /** + * Create a new SAXParseException from a message and a Locator. + * + *

This constructor is especially useful when an application is + * creating its own exception from within a {@link org.xml.sax.ContentHandler + * ContentHandler} callback.

+ * + * @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. + * + *

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}.

+ * + * @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. + * + *

This constructor is most useful for parser writers.

+ * + *

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.

+ * + * + * @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. + * + *

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}.

+ * + *

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.

+ * + * @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. + * + *

If the system identifier is a URL, it will have been resolved + * fully.

+ * + * @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. + * + *

The first line is line 1.

+ * + * @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. + * + *

The first column in a line is position 1.

+ * + * @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; + } + + + ////////////////////////////////////////////////////////////////////// + // 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 index 0000000..5a399fa --- /dev/null +++ b/src/org/xml/sax/XMLFilter.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

This method allows the application to link the filter to + * a parent reader (which may be another filter). The argument + * may not be null.

+ * + * @param parent The parent reader. + */ + public abstract void setParent (XMLReader parent); + + + /** + * Get the parent reader. + * + *

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.

+ * + * @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 index 0000000..9c150a0 --- /dev/null +++ b/src/org/xml/sax/XMLReader.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

Note: despite its name, this interface does + * not extend the standard Java {@link java.io.Reader Reader} + * interface, because reading XML is a fundamentally different activity + * than reading character data.

+ * + *

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.

+ * + *

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.

+ * + *

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):

+ * + *
    + *
  1. it adds a standard way to query and set features and + * properties; and
  2. + *
  3. it adds Namespace support, which is required for many + * higher-level XML standards.
  4. + *
+ * + *

There are adapters available to convert a SAX1 Parser to + * a SAX2 XMLReader and vice-versa.

+ * + * @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 +{ + + + //////////////////////////////////////////////////////////////////// + // Configuration. + //////////////////////////////////////////////////////////////////// + + + /** + * Look up the value of a feature flag. + * + *

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.)

+ * + *

All XMLReaders are required to recognize the + * http://xml.org/sax/features/namespaces and the + * http://xml.org/sax/features/namespace-prefixes feature names.

+ * + *

Typical usage is something like this:

+ * + *
+     * 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.");
+     * }
+     * 
+ * + *

Implementors are free (and encouraged) to invent their own features, + * using names built on their own URIs.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + * @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. + * + *

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.

+ * + *

XMLReaders are not required to recognize any specific + * property names, though an initial core set is documented for + * SAX2.

+ * + *

Implementors are free (and encouraged) to invent their own properties, + * using names built on their own URIs.

+ * + * @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. + * + *

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.

+ * + *

XMLReaders are not required to recognize setting + * any specific property names, though a core set is defined by + * SAX2.

+ * + *

This method is also the standard mechanism for setting + * extended handlers.

+ * + * @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; + + + + //////////////////////////////////////////////////////////////////// + // Event handlers. + //////////////////////////////////////////////////////////////////// + + + /** + * Allow an application to register an entity resolver. + * + *

If the application does not register an entity resolver, + * the XMLReader will perform its own default resolution.

+ * + *

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.

+ * + * @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. + * + *

If the application does not register a DTD handler, all DTD + * events reported by the SAX parser will be silently ignored.

+ * + *

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.

+ * + * @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. + * + *

If the application does not register a content handler, all + * content events reported by the SAX parser will be silently + * ignored.

+ * + *

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.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + * @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 (); + + + + //////////////////////////////////////////////////////////////////// + // Parsing. + //////////////////////////////////////////////////////////////////// + + /** + * Parse an XML document. + * + *

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).

+ * + *

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.) + *

+ * + *

During the parse, the XMLReader will provide information + * about the XML document through the registered event + * handlers.

+ * + *

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.

+ * + * @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). + * + *

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:

+ * + *
+     * parse(new InputSource(systemId));
+     * 
+ * + *

If the system identifier is a URL, it must be fully resolved + * by the application before it is passed to the parser.

+ * + * @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 index 0000000..5da34b9 --- /dev/null +++ b/src/org/xml/sax/demo/ByteStreamDemo.java @@ -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. + * + *

Usage: java -Dorg.xml.sax.parser=CLASSNAME ByteStreamDemo + * FILE

+ * + *

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.

+ * + * @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= " + + "SystemIdDemo "); + 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 index 0000000..8d97f0f --- /dev/null +++ b/src/org/xml/sax/demo/CharacterStreamDemo.java @@ -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. + * + *

Usage: java -Dorg.xml.sax.parser=CLASSNAME + * CharacterStreamDemo

+ * + *

The SAX parser will read the document from a character + * stream connected to an internal string.

+ * + *

Note that the Java implementation of SAX represents a + * character stream using a Reader.

+ * + * @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 = "" + + "\n" + + "Hello\n" + + "Hello, world!\n" + + "\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 index 0000000..66c6a08 --- /dev/null +++ b/src/org/xml/sax/demo/DemoHandler.java @@ -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. + * + *

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.

+ * + * @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 +{ + + + + ////////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.EntityResolver + ////////////////////////////////////////////////////////////////////// + + + /** + * Display requests for entity resolution. + * + *

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.

+ * + * @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; + } + + + + ////////////////////////////////////////////////////////////////////// + // 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 + '"'); + } + + + + ////////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.DocumentHandler + ////////////////////////////////////////////////////////////////////// + + + /** + * Print a message when the parser provides a locator. + * + *

Not all SAX parsers will provide a locator object.

+ * + * @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. + * + *

Display all attributes on separate lines, indented.

+ * + * @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); + } + + + + ////////////////////////////////////////////////////////////////////// + // 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. + * + *

Note: results are no longer reliable once a fatal error has + * been reported.

+ * + * @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() + + ')'); + } + + + + ////////////////////////////////////////////////////////////////////// + // 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 index 0000000..be3948c --- /dev/null +++ b/src/org/xml/sax/demo/EntityDemo.java @@ -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. + * + *

Usage: java -Dorg.xml.sax.parser=classname EntityDemo + * systemId

+ * + *

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:

+ * + *
+  * <!DOCTYPE doc [
+  *   <!ENTITY ent 
+  *     PUBLIC "-//megginson//TEXT Sample Entity//EN" "ent.xml">
+  * ]>
+  * <doc>
+  * <para>&ent;</para>
+  * </doc>
+  * 
+ * + *

The SAX parser will open a connection to the URI itself.

+ * + * @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= " + + "EntityDemo "); + 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(). + * + *

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.

+ * + *

The public identifier is safer than the system identifier, + * since the parser may have resolved the system identifier to + * an absolute URL.

+ * + * @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 index 0000000..cb1d679 --- /dev/null +++ b/src/org/xml/sax/ext/Attributes2.java @@ -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 http://xml.org/sax/features/use-attributes2 + * feature flag will have the value true. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + *
+ * + *

XMLReader implementations are not required to support this + * information, and it is not part of core-only SAX2 distributions.

+ * + *

Note that if an attribute was defaulted (!isSpecified()) + * it will of necessity also have been declared (isDeclared()) + * in the DTD. + * Similarly if an attribute's type is anything except CDATA, then it + * must have been declared. + *

+ * + * @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). + * + *

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 + * qName. + * + * @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. + * + *

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 + * qName. + * + * @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 index 0000000..ad24c78 --- /dev/null +++ b/src/org/xml/sax/ext/Attributes2Impl.java @@ -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. + * + *

+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + *
+ * + *

This is not part of core-only SAX2 distributions.

+ * + *

The specified 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 declared flag for each attribute will + * always be false, except for defaulted attributes (specified + * 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 declared flag to match. + *

+ * + * @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 + * specified and declared 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 declared). + * + *

This constructor is especially useful inside a + * {@link org.xml.sax.ContentHandler#startElement startElement} event.

+ * + * @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}. + * + *

Unless the attribute type 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 index 0000000..865e33c --- /dev/null +++ b/src/org/xml/sax/ext/DeclHandler.java @@ -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. + * + *

+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

Note that data-related DTD declarations (unparsed entities and + * notations) are already reported through the {@link + * org.xml.sax.DTDHandler DTDHandler} interface.

+ * + *

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.

+ * + *

To set the DeclHandler for an XML reader, use the + * {@link org.xml.sax.XMLReader#setProperty setProperty} method + * with the property name + * http://xml.org/sax/properties/declaration-handler + * 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.

+ * + * @since SAX 2.0 (extensions 1.0) + * @author David Megginson + * @version 2.0.1 (sax2r2) + */ +public interface DeclHandler +{ + + /** + * Report an element type declaration. + * + *

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.

+ * + * @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. + * + *

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.

+ * + *

The value will be the value as reported to applications, + * appropriately normalized and with entity and character + * references expanded.

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

Only the effective (first) declaration for each entity + * will be reported.

+ * + *

If the system identifier is a URL, the parser must resolve it + * fully before passing it to the application.

+ * + * @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 index 0000000..f2096ad --- /dev/null +++ b/src/org/xml/sax/ext/DefaultHandler2.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + *
+ * + *

Note: this class might yet learn that the + * ContentHandler.setDocumentLocator() call might be passed a + * {@link Locator2} object, and that the + * ContentHandler.startElement() 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 name and baseURI, and + * with the systemId 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 index 0000000..a1108a3 --- /dev/null +++ b/src/org/xml/sax/ext/EntityResolver2.java @@ -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 instead of the older (SAX 1.0) + * {@link EntityResolver#resolveEntity EntityResolver.resolveEntity()} method. + * + *

+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + *
+ * + *

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 + * http://xml.org/sax/features/use-entity-resolver2 feature flag + * set to true (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. + *

+ * + *

That supports three categories of application that modify entity + * resolution. Old Style applications won't know about this interface; + * they will provide an EntityResolver. + * Transitional Mode provide an EntityResolver2 and automatically + * get the benefit of its methods in any systems (parsers or other tools) + * supporting it, due to polymorphism. + * Both Old Style and Transitional Mode applications will + * work with any SAX2 parser. + * New style 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. + *

+ * + * @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.

+ * + *

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:

+ * + *
+     * ... 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", ...);
+     * 
+ * + *

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.

+ * + *

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.

+ * + *

Warning: 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. + *

+ * + * @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 OASIS XML Catalogs specification.

+ * + *

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.

+ * + *

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.

+ * + * @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 "<" 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 index 0000000..d63d87f --- /dev/null +++ b/src/org/xml/sax/ext/LexicalHandler.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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.

+ * + *

To set the LexicalHandler for an XML reader, use the + * {@link org.xml.sax.XMLReader#setProperty setProperty} method + * with the property name + * http://xml.org/sax/properties/lexical-handler + * 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.

+ * + * @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. + * + *

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.

+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

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 + * http://xml.org/sax/features/lexical-handler/parameter-entities + * feature to query or control the reporting of parameter entities.

+ * + *

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]".

+ * + *

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.

+ * + *

Note that skipped entities will be reported through the + * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity} + * event, which is part of the ContentHandler interface.

+ * + *

Because of the streaming event model that SAX uses, some + * entity boundaries cannot be reported under any + * circumstances:

+ * + *
    + *
  • general entities within attribute values
  • + *
  • parameter entities within declarations
  • + *
+ * + *

These will be silently expanded, with no indication of where + * the original entity boundaries were.

+ * + *

Note also that the boundaries of character references (which + * are not really entities anyway) are not reported.

+ * + *

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. + * + *

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.

+ * + * @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. + * + *

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).

+ * + * @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 index 0000000..6de9a16 --- /dev/null +++ b/src/org/xml/sax/ext/Locator2.java @@ -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 + * http://xml.org/sax/features/use-locator2 feature + * flag will have the value true. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + *
+ * + *

XMLReader implementations are not required to support this + * information, and it is not part of core-only SAX2 distributions.

+ * + * @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 + * <?xml version='...' ...?> 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 <?xml ...encoding='...'?> 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. + * + *

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. + * + *

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. + * + *

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 index 0000000..89ea4ba --- /dev/null +++ b/src/org/xml/sax/ext/Locator2Impl.java @@ -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. + * + *

+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + *
+ * + *

This is not part of core-only SAX2 distributions.

+ * + * @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 + * encoding and versionstrings are copied, + * otherwise they set to null. + * + * @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 index 0000000..fba22ea --- /dev/null +++ b/src/org/xml/sax/helpers/AttributeListImpl.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

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:

+ * + *
+ * 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);
+ *   [...]
+ * }
+ * 
+ * + *

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.

+ * + * @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. + * + *

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.

+ * + * @see #addAttribute + * @see #clear + */ + public AttributeListImpl () + { + } + + + /** + * Construct a persistent copy of an existing attribute list. + * + *

This constructor is most useful for application writers, + * who will use it to create a persistent copy of an existing + * attribute list.

+ * + * @param atts The attribute list to copy + * @see org.xml.sax.DocumentHandler#startElement + */ + public AttributeListImpl (AttributeList atts) + { + setAttributeList(atts); + } + + + + //////////////////////////////////////////////////////////////////// + // Methods specific to this class. + //////////////////////////////////////////////////////////////////// + + + /** + * Set the attribute list, discarding previous contents. + * + *

This method allows an application writer to reuse an + * attribute list easily.

+ * + * @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. + * + *

This method is provided for SAX parser writers, to allow them + * to build up an attribute list incrementally before delivering + * it to the application.

+ * + * @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. + * + *

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.

+ * + *

If the requested attribute is not in the list, this is + * a no-op.

+ * + * @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. + * + *

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.

+ * + * @see org.xml.sax.DocumentHandler#startElement + */ + public void clear () + { + names.removeAllElements(); + types.removeAllElements(); + values.removeAllElements(); + } + + + + //////////////////////////////////////////////////////////////////// + // 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)); + } + + + + //////////////////////////////////////////////////////////////////// + // 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 index 0000000..fac6b26 --- /dev/null +++ b/src/org/xml/sax/helpers/AttributesImpl.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

There are two typical uses of this class:

+ * + *
    + *
  1. to take a persistent snapshot of an Attributes object + * in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or
  2. + *
  3. to construct or modify an Attributes object in a SAX2 driver or filter.
  4. + *
+ * + *

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.

+ * + * @since SAX 2.0 + * @author David Megginson + * @version 2.0.1 (sax2r2) + */ +public class AttributesImpl implements Attributes +{ + + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + + + /** + * Construct a new, empty AttributesImpl object. + */ + public AttributesImpl () + { + length = 0; + data = null; + } + + + /** + * Copy an existing Attributes object. + * + *

This constructor is especially useful inside a + * {@link org.xml.sax.ContentHandler#startElement startElement} event.

+ * + * @param atts The existing Attributes object. + */ + public AttributesImpl (Attributes atts) + { + setAttributes(atts); + } + + + + //////////////////////////////////////////////////////////////////// + // 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. + * + *

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.

+ * + * @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; + } + + + + //////////////////////////////////////////////////////////////////// + // Manipulators. + //////////////////////////////////////////////////////////////////// + + + /** + * Clear the attribute list for reuse. + * + *

Note that little memory is freed by this call: + * the current array is kept so it can be + * reused.

+ */ + public void clear () + { + if (data != null) { + for (int i = 0; i < (length * 5); i++) + data [i] = null; + } + length = 0; + } + + + /** + * Copy an entire Attributes object. + * + *

It may be more efficient to reuse an existing object + * rather than constantly allocating new ones.

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

For the sake of speed, this method does no checking + * for name conflicts or well-formedness: such checks are the + * responsibility of the application.

+ * + * @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); + } + } + + + + //////////////////////////////////////////////////////////////////// + // 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); + } + + + + //////////////////////////////////////////////////////////////////// + // 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 index 0000000..83bde62 --- /dev/null +++ b/src/org/xml/sax/helpers/DefaultHandler.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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:

+ * + *
    + *
  • {@link org.xml.sax.EntityResolver EntityResolver}
  • + *
  • {@link org.xml.sax.DTDHandler DTDHandler}
  • + *
  • {@link org.xml.sax.ContentHandler ContentHandler}
  • + *
  • {@link org.xml.sax.ErrorHandler ErrorHandler}
  • + *
+ * + *

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.

+ * + *

This class replaces the deprecated SAX1 + * {@link org.xml.sax.HandlerBase HandlerBase} class.

+ * + * @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 +{ + + + //////////////////////////////////////////////////////////////////// + // Default implementation of the EntityResolver interface. + //////////////////////////////////////////////////////////////////// + + /** + * Resolve an external entity. + * + *

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.

+ * + * @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; + } + + + + //////////////////////////////////////////////////////////////////// + // Default implementation of DTDHandler interface. + //////////////////////////////////////////////////////////////////// + + + /** + * Receive notification of a notation declaration. + * + *

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.

+ * + * @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. + * + *

By default, do nothing. Application writers may override this + * method in a subclass to keep track of the unparsed entities + * declared in a document.

+ * + * @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 + } + + + + //////////////////////////////////////////////////////////////////// + // Default implementation of ContentHandler interface. + //////////////////////////////////////////////////////////////////// + + + /** + * Receive a Locator object for document events. + * + *

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.

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

By default, do nothing. Application writers may override this + * method in a subclass to take specific actions at the end of + * each prefix mapping.

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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).

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

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.

+ * + * @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 + } + + + + //////////////////////////////////////////////////////////////////// + // Default implementation of the ErrorHandler interface. + //////////////////////////////////////////////////////////////////// + + + /** + * Receive notification of a parser warning. + * + *

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.

+ * + * @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. + * + *

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.

+ * + * @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. + * + *

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.

+ * + * @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 index 0000000..90eab7e --- /dev/null +++ b/src/org/xml/sax/helpers/LocatorImpl.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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:

+ * + *
+ * 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);
+ * }
+ *
+ * + *

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.

+ * + * @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. + * + *

This will not normally be useful, since the main purpose + * of this class is to make a snapshot of an existing Locator.

+ */ + public LocatorImpl () + { + } + + + /** + * Copy constructor. + * + *

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).

+ * + * @param locator The locator to copy. + */ + public LocatorImpl (Locator locator) + { + setPublicId(locator.getPublicId()); + setSystemId(locator.getSystemId()); + setLineNumber(locator.getLineNumber()); + setColumnNumber(locator.getColumnNumber()); + } + + + + //////////////////////////////////////////////////////////////////// + // 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; + } + + + + //////////////////////////////////////////////////////////////////// + // 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; + } + + + + //////////////////////////////////////////////////////////////////// + // 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 index 0000000..3c15f5e --- /dev/null +++ b/src/org/xml/sax/helpers/NamespaceSupport.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

Namespace support objects are reusable, but the reset method + * must be invoked between each session.

+ * + *

Here is a simple session:

+ * + *
+ * 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();
+ * 
+ * + *

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.

+ * + *

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 +{ + + + //////////////////////////////////////////////////////////////////// + // Constants. + //////////////////////////////////////////////////////////////////// + + + /** + * The XML Namespace URI as a constant. + * The value is http://www.w3.org/XML/1998/namespace + * as defined in the "Namespaces in XML" * recommendation. + * + *

This is the Namespace URI that is automatically mapped + * to the "xml" prefix.

+ */ + public final static String XMLNS = + "http://www.w3.org/XML/1998/namespace"; + + + /** + * The namespace declaration URI as a constant. + * The value is http://www.w3.org/xmlns/2000/, 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. + * + * + *

This is the Namespace URI that is optionally applied to + * xmlns and xmlns:* attributes, which are used to + * declare namespaces.

+ * + * @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(); + + + //////////////////////////////////////////////////////////////////// + // Constructor. + //////////////////////////////////////////////////////////////////// + + + /** + * Create a new Namespace support object. + */ + public NamespaceSupport () + { + reset(); + } + + + + //////////////////////////////////////////////////////////////////// + // Context management. + //////////////////////////////////////////////////////////////////// + + + /** + * Reset this Namespace support object for reuse. + * + *

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. + *

+ * + * @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. + * + *

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 + * ContentHandler.startElement() callback is the right place. + * For elements with such a declaration, it'd done in the first + * ContentHandler.startPrefixMapping() 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 ContentHandler.startElement() + * does that, it always clears the flag. + * + *

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 + * ContentHandler.startPrefixMapping() 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 + * ContentHandler.startElement() callback is available, + * so it can then be made. + * + *

The Namespace support object always starts with a base context + * already in force: in this context, only the "xml" prefix is + * declared.

+ * + * @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. + * + *

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.

+ * + *

You must not attempt to declare additional Namespace + * prefixes after popping a context, unless you push another + * context first.

+ * + * @see #pushContext + */ + public void popContext () + { + contexts[contextPos].clear(); + contextPos--; + if (contextPos < 0) { + throw new EmptyStackException(); + } + currentContext = contexts[contextPos]; + } + + + + //////////////////////////////////////////////////////////////////// + // 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. + * + *

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.

+ * + *

To declare the default element Namespace, use the empty string as + * the prefix.

+ * + *

Note that you must not declare a prefix after + * you've pushed and popped another Namespace context, or + * treated the declarations phase as complete by processing + * a prefixed name.

+ * + *

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.

+ * + * @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()}. + * + *

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:

+ * + *
+ *
parts[0]
+ *
The Namespace URI, or an empty string if none is + * in use.
+ *
parts[1]
+ *
The local name (without prefix).
+ *
parts[2]
+ *
The original raw name.
+ *
+ * + *

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.

+ * + *

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.

+ * + * @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. + * + *

This method looks up the prefix in the current context. + * Use the empty string ("") for the default Namespace.

+ * + * @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. + * + *

Note: 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 "".

+ * + * @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. + * + *

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.

+ * + *

Note: this will never return the empty (default) prefix; + * to check for a default prefix, use the {@link #getURI getURI} + * method with an argument of "".

+ * + * @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. + * + *

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.

+ * + *

Note: the empty (default) prefix is never included + * in this enumeration; to check for the presence of a default + * Namespace, use the {@link #getURI getURI} method with an + * argument of "".

+ * + * @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. + * + *

The empty (default) prefix will be included in this + * enumeration; note that this behaviour differs from that of + * {@link #getPrefix} and {@link #getPrefixes}.

+ * + * @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; } + + + + //////////////////////////////////////////////////////////////////// + // Internal state. + //////////////////////////////////////////////////////////////////// + + private Context contexts[]; + private Context currentContext; + private int contextPos; + private boolean namespaceDeclUris; + + + //////////////////////////////////////////////////////////////////// + // Internal classes. + //////////////////////////////////////////////////////////////////// + + /** + * Internal class for a single Namespace context. + * + *

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. + *

+ */ + 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. + * + *

Since many prefixes may be mapped to the same URI, + * the return value may be unreliable.

+ * + * @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. + * + *

The default prefix, if in force, is not + * returned, and will have to be checked for separately.

+ * + * @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(); + } + } + + + + //////////////////////////////////////////////////////////////// + // Internal methods. + //////////////////////////////////////////////////////////////// + + + /** + * Copy on write for the internal tables in this context. + * + *

This class is optimized for the normal case where most + * elements do not contain Namespace declarations.

+ */ + 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; + } + + + + //////////////////////////////////////////////////////////////// + // Protected state. + //////////////////////////////////////////////////////////////// + + Hashtable prefixTable; + Hashtable uriTable; + Hashtable elementNameTable; + Hashtable attributeNameTable; + String defaultNS = null; + boolean declsOK = true; + + + + //////////////////////////////////////////////////////////////// + // 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 index 0000000..6fc34d6 --- /dev/null +++ b/src/org/xml/sax/helpers/NewInstance.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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).

+ * + *

This code is designed to compile and run on JDK version 1.1 and later + * including versions of Java 2.

+ * + * @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 index 0000000..b723219 --- /dev/null +++ b/src/org/xml/sax/helpers/ParserAdapter.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + *

This adapter does not test for duplicate Namespace-qualified + * attribute names.

+ * + * @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 +{ + + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + + + /** + * Construct a new parser adapter. + * + *

Use the "org.xml.sax.parser" property to locate the + * embedded SAX1 driver.

+ * + * @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. + * + *

Note that the embedded parser cannot be changed once the + * adapter is created; to embed a different parser, allocate + * a new ParserAdapter.

+ * + * @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(); + } + + + + //////////////////////////////////////////////////////////////////// + // 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. + * + *

The only features recognized are namespaces and + * namespace-prefixes.

+ * + * @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. + * + *

The only features recognized are namespaces and + * namespace-prefixes.

+ * + * @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. + * + *

No properties are currently recognized.

+ * + * @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. + * + *

No properties are currently recognized.

+ * + * @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. + * + *

If necessary, perform Namespace processing.

+ * + * @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); + } + } + + + + //////////////////////////////////////////////////////////////////// + // 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. + * + *

If the name has an undeclared prefix, use only the qname + * and make an ErrorHandler.error callback in case the app is + * interested.

+ * + * @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. + * + *

Use this method to detect illegal feature or + * property changes.

+ * + * @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"); + + } + } + + + + //////////////////////////////////////////////////////////////////// + // 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; + + + + //////////////////////////////////////////////////////////////////// + // Inner class to wrap an AttributeList when not doing NS proc. + //////////////////////////////////////////////////////////////////// + + + /** + * Adapt a SAX1 AttributeList as a SAX2 Attributes object. + * + *

This class is in the Public Domain, and comes with NO + * WARRANTY of any kind.

+ * + *

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.

+ */ + final class AttributeListAdapter implements Attributes + { + + /** + * Construct a new adapter. + */ + AttributeListAdapter () + { + } + + + /** + * Set the embedded AttributeList. + * + *

This method must be invoked before any of the others + * can be used.

+ * + * @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 index 0000000..8ca7838 --- /dev/null +++ b/src/org/xml/sax/helpers/ParserFactory.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

Note: 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.

+ * + *

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.

+ * + *

Note that the application still requires an XML parser that + * implements SAX1.

+ * + * @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. + * + *

The named class must exist and must implement the + * {@link org.xml.sax.Parser Parser} interface.

+ * + * @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. + * + *

The named class must exist and must implement the + * {@link org.xml.sax.Parser Parser} interface.

+ * + * @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 index 0000000..6f329d9 --- /dev/null +++ b/src/org/xml/sax/helpers/XMLFilterImpl.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + * @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 +{ + + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + + + /** + * Construct an empty XML filter, with no parent. + * + *

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}.

+ * + * @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); + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.XMLFilter. + //////////////////////////////////////////////////////////////////// + + + /** + * Set the parent reader. + * + *

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.

+ * + *

If there is no parent reader set, any attempt to parse + * or to set or get a feature or property will fail.

+ * + * @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; + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.XMLReader. + //////////////////////////////////////////////////////////////////// + + + /** + * Set the value of a feature. + * + *

This will always fail if the parent is null.

+ * + * @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. + * + *

This will always fail if the parent is null.

+ * + * @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. + * + *

This will always fail if the parent is null.

+ * + * @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)); + } + + + + //////////////////////////////////////////////////////////////////// + // 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; + } + } + + + + //////////////////////////////////////////////////////////////////// + // 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); + } + } + + + + //////////////////////////////////////////////////////////////////// + // 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); + } + } + + + + //////////////////////////////////////////////////////////////////// + // 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); + } + } + + + + //////////////////////////////////////////////////////////////////// + // Internal methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Set up before a parse. + * + *

Before every parse, check whether the parent is + * non-null, and re-register the filter for all of the + * events.

+ */ + 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); + } + + + + //////////////////////////////////////////////////////////////////// + // 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 index 0000000..8a587c4 --- /dev/null +++ b/src/org/xml/sax/helpers/XMLReaderAdapter.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

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.

+ * + * @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 +{ + + + //////////////////////////////////////////////////////////////////// + // Constructor. + //////////////////////////////////////////////////////////////////// + + + /** + * Create a new adapter. + * + *

Use the "org.xml.sax.driver" property to locate the SAX2 + * driver to embed.

+ * + * @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. + * + *

Create a new adapter, wrapped around a SAX2 XMLReader. + * The adapter will make the XMLReader act like a SAX1 + * Parser.

+ * + * @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. + * + *

This is not supported in SAX2, and will always throw + * an exception.

+ * + * @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. + * + *

Note that the SAX1 document handler has no Namespace + * support.

+ * + * @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. + * + *

This method will throw an exception if the embedded + * XMLReader does not support the + * http://xml.org/sax/features/namespace-prefixes property.

+ * + * @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. + * + *

This method will throw an exception if the embedded + * XMLReader does not support the + * http://xml.org/sax/features/namespace-prefixes property.

+ * + * @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); + } + + + + //////////////////////////////////////////////////////////////////// + // 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 + { + } + + + + //////////////////////////////////////////////////////////////////// + // Internal state. + //////////////////////////////////////////////////////////////////// + + XMLReader xmlReader; + DocumentHandler documentHandler; + AttributesAdapter qAtts; + + + + //////////////////////////////////////////////////////////////////// + // 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 index 0000000..f50be7a --- /dev/null +++ b/src/org/xml/sax/helpers/XMLReaderFactory.java @@ -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. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

This class contains static methods for creating an XML reader + * from an explicit class name, or based on runtime defaults:

+ * + *
+ * try {
+ *   XMLReader myReader = XMLReaderFactory.createXMLReader();
+ * } catch (SAXException e) {
+ *   System.err.println(e.getMessage());
+ * }
+ * 
+ * + *

Note to Distributions bundled with parsers: + * You should modify the implementation of the no-arguments + * createXMLReader 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 org.xml.sax.driver so + * those configuration mechanisms would see it.

+ * + * @since SAX 2.0 + * @author David Megginson, David Brownell + * @version 2.0.1 (sax2r2) + */ +final public class XMLReaderFactory +{ + /** + * Private constructor. + * + *

This constructor prevents the class from being instantiated.

+ */ + 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:

    + * + *
  • If the system property org.xml.sax.driver + * has a value, that is used as an XMLReader class name.
  • + * + *
  • The JAR "Services API" is used to look for a class name + * in the META-INF/services/org.xml.sax.driver file in + * jarfiles available to the runtime.
  • + * + *
  • 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.
  • + * + *
  • 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 org.xml.sax.parser system + * property will often be usable.)
  • + * + *
+ * + *

In environments such as small embedded systems, which can not + * support that flexibility, other mechanisms to determine the default + * may be used.

+ * + *

Note that many Java environments allow system properties to be + * initialized on a command line. This means that in most cases + * 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. + *

+ * + * @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. + * + *

Given a class name, this method attempts to load + * and instantiate the class as an XML reader.

+ * + *

Note that this method will not be usable in environments where + * the caller (perhaps an applet) is not permitted to load classes + * dynamically.

+ * + * @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 index 0000000..8f323c0 --- /dev/null +++ b/src/org/xml/sax/helpers/package.html @@ -0,0 +1,11 @@ + + + + +

This package contains "helper" classes, including +support for bootstrapping SAX-based applications. + +

See http://www.saxproject.org +for more information about SAX.

+ + diff --git a/swingjs/README.txt b/swingjs/README.txt new file mode 100644 index 0000000..e16c383 --- /dev/null +++ b/swingjs/README.txt @@ -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 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 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 index 0000000..5005060 --- /dev/null +++ b/template.html @@ -0,0 +1,31 @@ + + + +SwingJS test _NAME_ + + + + + +
+
+This is System.out. clear it
Add ?j2snocore to URL to see full class list; ?j2sdebug to use uncompressed j2s/core files
get _j2sClassList.txt +
+ + diff --git a/tools/ant-contrib.jar b/tools/ant-contrib.jar new file mode 100644 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 index 0000000..53037b0 Binary files /dev/null and b/tools/closure_compiler.jar differ