diff --git a/sources/net.sf.j2s.core/META-INF/MANIFEST.MF b/sources/net.sf.j2s.core/META-INF/MANIFEST.MF index 9abb95c89..2b5ca3e78 100644 --- a/sources/net.sf.j2s.core/META-INF/MANIFEST.MF +++ b/sources/net.sf.j2s.core/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Java2Script Core Bundle-SymbolicName: net.sf.j2s.core;singleton:=true -Bundle-Version: 3.2.5 +Bundle-Version: 3.2.6 Bundle-Activator: net.sf.j2s.core.CorePlugin Bundle-Vendor: j2s.sourceforge.net Require-Bundle: org.eclipse.core.runtime, diff --git a/sources/net.sf.j2s.core/dist/swingjs/SwingJS-site.zip b/sources/net.sf.j2s.core/dist/swingjs/SwingJS-site.zip index d39f23180..01d6b0802 100644 Binary files a/sources/net.sf.j2s.core/dist/swingjs/SwingJS-site.zip and b/sources/net.sf.j2s.core/dist/swingjs/SwingJS-site.zip differ diff --git a/sources/net.sf.j2s.core/dist/swingjs/net.sf.j2s.core.jar b/sources/net.sf.j2s.core/dist/swingjs/net.sf.j2s.core.jar index 94e28338b..2fe0400a2 100644 Binary files a/sources/net.sf.j2s.core/dist/swingjs/net.sf.j2s.core.jar and b/sources/net.sf.j2s.core/dist/swingjs/net.sf.j2s.core.jar differ diff --git a/sources/net.sf.j2s.core/dist/swingjs/timestamp b/sources/net.sf.j2s.core/dist/swingjs/timestamp index f296b1996..b36a14d42 100644 --- a/sources/net.sf.j2s.core/dist/swingjs/timestamp +++ b/sources/net.sf.j2s.core/dist/swingjs/timestamp @@ -1 +1 @@ -20191215102844 +20200103220342 diff --git a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.5/net.sf.j2s.core.jar b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.5/net.sf.j2s.core.jar index 94e28338b..057eacdc8 100644 Binary files a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.5/net.sf.j2s.core.jar and b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.5/net.sf.j2s.core.jar differ diff --git a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.5/timestamp b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.5/timestamp index f296b1996..4db8b2011 100644 --- a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.5/timestamp +++ b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.5/timestamp @@ -1 +1 @@ -20191215102844 +20191219043415 diff --git a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/DEV_NOTES.txt b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/DEV_NOTES.txt new file mode 100644 index 000000000..ffbc7bf86 --- /dev/null +++ b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/DEV_NOTES.txt @@ -0,0 +1,10 @@ +This is sources/net.sf.j2s.java.core/dist/DEV_NOTES.txt + +_j2sclasslist.txt + +the list of .js files concatenated into coreswingjs.js and minified to coreswingjs.z.js + + +SwingJS-site.zip + +the full site directory for SwingJS including all files not in the test/ directory. diff --git a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/SwingJS-site.zip b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/SwingJS-site.zip new file mode 100644 index 000000000..01d6b0802 Binary files /dev/null and b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/SwingJS-site.zip differ diff --git a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/_j2sclasslist.txt b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/_j2sclasslist.txt new file mode 100644 index 000000000..23c7726ba --- /dev/null +++ b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/_j2sclasslist.txt @@ -0,0 +1,408 @@ +java/applet/Applet.js +java/applet/AppletContext.js +java/applet/AppletStub.js +java/applet/JSApplet.js +java/awt/ActiveEvent.js +java/awt/Adjustable.js +java/awt/AWTEvent.js +java/awt/AWTEventMulticaster.js +java/awt/AWTKeyStroke.js +java/awt/BasicStroke.js +java/awt/BorderLayout.js +java/awt/Button.js +java/awt/Color.js +java/awt/color/ColorSpace.js +java/awt/Component.js +java/awt/ComponentOrientation.js +java/awt/ContainerOrderFocusTraversalPolicy.js +java/awt/Container.js +java/awt/Cursor.js +java/awt/DefaultFocusTraversalPolicy.js +java/awt/DefaultKeyboardFocusManager.js +java/awt/Dialog.js +java/awt/Dimension.js +java/awt/dnd/peer/DropTargetPeer.js +java/awt/event/ActionListener.js +java/awt/event/AdjustmentEvent.js +java/awt/event/AdjustmentListener.js +java/awt/event/AWTEventListener.js +java/awt/event/ComponentAdapter.js +java/awt/event/ComponentEvent.js +java/awt/event/ComponentListener.js +java/awt/event/ContainerListener.js +java/awt/event/FocusEvent.js +java/awt/event/FocusListener.js +java/awt/event/HierarchyBoundsListener.js +java/awt/event/HierarchyListener.js +java/awt/event/InputEvent.js +java/awt/event/InputMethodListener.js +java/awt/event/InvocationEvent.js +java/awt/event/ItemEvent.js +java/awt/event/ItemListener.js +java/awt/event/KeyListener.js +java/awt/event/MouseEvent.js +java/awt/event/MouseListener.js +java/awt/event/MouseMotionListener.js +java/awt/event/MouseWheelListener.js +java/awt/event/TextListener.js +java/awt/event/WindowAdapter.js +java/awt/event/WindowEvent.js +java/awt/event/WindowFocusListener.js +java/awt/event/WindowListener.js +java/awt/event/WindowStateListener.js +java/awt/EventDispatchThread.js +java/awt/EventFilter.js +java/awt/EventQueue.js +java/awt/EventQueueItem.js +java/awt/FlowLayout.js +java/awt/FocusTraversalPolicy.js +java/awt/Font.js +java/awt/font/FontRenderContext.js +java/awt/FontMetrics.js +java/awt/Frame.js +java/awt/geom/AffineTransform.js +java/awt/geom/Dimension2D.js +java/awt/geom/Path2D.js +java/awt/geom/PathIterator.js +java/awt/geom/Point2D.js +java/awt/geom/Rectangle2D.js +java/awt/geom/RectangularShape.js +java/awt/geom/RectIterator.js +java/awt/GraphicsCallback.js +java/awt/GraphicsConfiguration.js +java/awt/GraphicsDevice.js +java/awt/GraphicsEnvironment.js +java/awt/Image.js +java/awt/image/ImageObserver.js +java/awt/Insets.js +java/awt/ItemSelectable.js +java/awt/JSComponent.js +java/awt/JSDialog.js +java/awt/JSFrame.js +java/awt/JSPanel.js +java/awt/KeyboardFocusManager.js +java/awt/KeyEventDispatcher.js +java/awt/KeyEventPostProcessor.js +java/awt/Label.js +java/awt/LayoutManager.js +java/awt/LayoutManager2.js +java/awt/LightweightDispatcher.js +java/awt/Paint.js +java/awt/Panel.js +java/awt/peer/ComponentPeer.js +java/awt/peer/ContainerPeer.js +java/awt/peer/FramePeer.js +java/awt/peer/KeyboardFocusManagerPeer.js +java/awt/peer/LightweightPeer.js +java/awt/peer/WindowPeer.js +java/awt/Point.js +java/awt/Queue.js +java/awt/Rectangle.js +java/awt/RenderingHints.js +java/awt/Scrollbar.js +java/awt/ScrollPane.js +java/awt/Shape.js +java/awt/Stroke.js +java/awt/TextArea.js +java/awt/TextComponent.js +java/awt/TextField.js +java/awt/Toolkit.js +java/awt/Transparency.js +java/awt/Window.js +java/beans/ChangeListenerMap.js +java/beans/PropertyChangeEvent.js +java/beans/PropertyChangeListener.js +java/beans/PropertyChangeSupport.js +java/lang/AbstractStringBuilder.js +java/lang/Class.js +java/lang/Enum.js +java/lang/Iterable.js +java/lang/reflect/Constructor.js +java/lang/reflect/Method.js +java/lang/StringBuffer.js +java/lang/StringBuilder.js +java/lang/Thread.js +java/lang/ThreadGroup.js +java/math/RoundingMode.js +java/net/URL.js +java/net/URLStreamHandlerFactory.js +java/net/HttpURLConnection.js +java/net/URLStreamHandler.js +javax/net/ssl/HttpsUrlConnection.js +java/text/CharacterIterator.js +java/text/DecimalFormat.js +java/text/DecimalFormatSymbols.js +java/text/DigitList.js +java/text/FieldPosition.js +java/text/Format.js +java/text/NumberFormat.js +java/util/AbstractCollection.js +java/util/AbstractList.js +java/util/AbstractMap.js +java/util/AbstractSequentialList.js +java/util/AbstractSet.js +java/util/ArrayList.js +java/util/Arrays.js +java/util/Collection.js +java/util/Collections.js +java/util/Comparator.js +java/util/Deque.js +java/util/Dictionary.js +java/util/Enumeration.js +java/util/EventListener.js +java/util/EventObject.js +java/util/HashMap.js +java/util/HashSet.js +java/util/Hashtable.js +java/util/IdentityHashMap.js +java/util/Iterator.js +java/util/LinkedHashMap.js +java/util/LinkedList.js +java/util/List.js +java/util/ListResourceBundle.js +java/util/Locale.js +java/util/Map.js +java/util/Objects.js +java/util/Queue.js +java/util/Random.js +java/util/RandomAccess.js +java/util/ResourceBundle.js +java/util/Set.js +java/util/TimSort.js +java/util/Vector.js +javajs/api/JSFunction.js +javajs/util/AjaxURLConnection.js +javajs/util/AjaxURLStreamHandlerFactory.js +javajs/util/AU.js +javajs/util/JSThread.js +javajs/util/Lst.js +javajs/util/PT.js +javajs/util/Rdr.js +javajs/util/SB.js +javax/swing/AbstractAction.js +javax/swing/AbstractButton.js +javax/swing/AbstractListModel.js +javax/swing/Action.js +javax/swing/ActionMap.js +javax/swing/AncestorNotifier.js +javax/swing/ArrayTable.js +javax/swing/border/AbstractBorder.js +javax/swing/border/BevelBorder.js +javax/swing/border/Border.js +javax/swing/border/CompoundBorder.js +javax/swing/border/EmptyBorder.js +javax/swing/border/EtchedBorder.js +javax/swing/border/LineBorder.js +javax/swing/border/TitledBorder.js +javax/swing/BorderFactory.js +javax/swing/BoundedRangeModel.js +javax/swing/BoxLayout.js +javax/swing/ButtonGroup.js +javax/swing/ButtonModel.js +javax/swing/ClientPropertyKey.js +javax/swing/ComboBoxModel.js +javax/swing/DefaultBoundedRangeModel.js +javax/swing/DefaultButtonModel.js +javax/swing/DefaultComboBoxModel.js +javax/swing/DefaultSingleSelectionModel.js +javax/swing/DropMode.js +javax/swing/event/AncestorEvent.js +javax/swing/event/AncestorListener.js +javax/swing/event/CaretEvent.js +javax/swing/event/CaretListener.js +javax/swing/event/ChangeEvent.js +javax/swing/event/ChangeListener.js +javax/swing/event/DocumentEvent.js +javax/swing/event/DocumentListener.js +javax/swing/event/EventListenerList.js +javax/swing/event/ListDataEvent.js +javax/swing/event/ListDataListener.js +javax/swing/event/UndoableEditEvent.js +javax/swing/event/UndoableEditListener.js +javax/swing/FocusManager.js +javax/swing/InternalFrameFocusTraversalPolicy.js +javax/swing/LayoutComparator.js +javax/swing/LayoutFocusTraversalPolicy.js +javax/swing/SortingFocusTraversalPolicy.js +javax/swing/SwingContainerOrderFocusTraversalPolicy.js +javax/swing/SwingDefaultFocusTraversalPolicy.js +javax/swing/Icon.js +javax/swing/ImageIcon.js +javax/swing/InputMap.js +javax/swing/JApplet.js +javax/swing/JButton.js +javax/swing/JCheckBox.js +javax/swing/JCheckBoxMenuItem.js +javax/swing/JComboBox.js +javax/swing/JComponent.js +javax/swing/JFrame.js +javax/swing/JLabel.js +javax/swing/JLayeredPane.js +javax/swing/JMenu.js +javax/swing/JMenuBar.js +javax/swing/JMenuItem.js +javax/swing/JPanel.js +javax/swing/JPopupMenu.js +javax/swing/JRadioButtonMenuItem.js +javax/swing/JRootPane.js +javax/swing/JScrollBar.js +javax/swing/JScrollPane.js +javax/swing/JSeparator.js +javax/swing/JTextArea.js +javax/swing/JTextField.js +javax/swing/JToggleButton.js +javax/swing/JViewport.js +javax/swing/KeyboardManager.js +javax/swing/KeyStroke.js +javax/swing/ListModel.js +javax/swing/LookAndFeel.js +javax/swing/MenuElement.js +javax/swing/MutableComboBoxModel.js +javax/swing/plaf/ActionMapUIResource.js +javax/swing/plaf/basic/BasicBorders.js +javax/swing/plaf/BorderUIResource.js +javax/swing/plaf/ColorUIResource.js +javax/swing/plaf/ComponentUI.js +javax/swing/plaf/DimensionUIResource.js +javax/swing/plaf/FontUIResource.js +javax/swing/plaf/InputMapUIResource.js +javax/swing/plaf/InsetsUIResource.js +javax/swing/plaf/UIResource.js +javax/swing/RepaintManager.js +javax/swing/RootPaneContainer.js +javax/swing/Scrollable.js +javax/swing/ScrollPaneConstants.js +javax/swing/ScrollPaneLayout.js +javax/swing/SingleSelectionModel.js +javax/swing/SizeRequirements.js +javax/swing/SwingConstants.js +javax/swing/SwingPaintEventDispatcher.js +javax/swing/SwingUtilities.js +javax/swing/text/AbstractDocument.js +javax/swing/text/AttributeSet.js +javax/swing/text/Caret.js +javax/swing/text/DefaultCaret.js +javax/swing/text/DefaultEditorKit.js +javax/swing/text/Document.js +javax/swing/text/EditorKit.js +javax/swing/text/Element.js +javax/swing/text/GapContent.js +javax/swing/text/GapVector.js +javax/swing/text/JTextComponent.js +javax/swing/text/MutableAttributeSet.js +javax/swing/text/PlainDocument.js +javax/swing/text/PlainView.js +javax/swing/text/Position.js +javax/swing/text/Segment.js +javax/swing/text/SegmentCache.js +javax/swing/text/SimpleAttributeSet.js +javax/swing/text/Style.js +javax/swing/text/StyleConstants.js +javax/swing/text/StyleContext.js +javax/swing/text/TabExpander.js +javax/swing/text/TextAction.js +javax/swing/text/Utilities.js +javax/swing/text/View.js +javax/swing/tree/TreeNode.js +javax/swing/UIDefaults.js +javax/swing/UIManager.js +javax/swing/undo/AbstractUndoableEdit.js +javax/swing/undo/CompoundEdit.js +javax/swing/undo/UndoableEdit.js +javax/swing/ViewportLayout.js +javax/swing/WindowConstants.js +sun/awt/AppContext.js +sun/awt/AWTAutoShutdown.js +sun/awt/CausedFocusEvent.js +sun/awt/ComponentFactory.js +sun/awt/KeyboardFocusManagerPeerProvider.js +sun/awt/MostRecentKeyValue.js +sun/awt/MostRecentThreadAppContext.js +sun/awt/PaintEventDispatcher.js +sun/awt/PostEventQueue.js +sun/awt/RequestFocusController.js +sun/awt/SunToolkit.js +sun/awt/WindowClosingListener.js +sun/awt/WindowClosingSupport.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/swing/DefaultLookup.js +sun/swing/SwingLazyValue.js +sun/text/resources/FormatData.js +sun/text/resources/FormatData_en.js +sun/util/resources/LocaleData.js +swingjs/a2s/A2SContainer.js +swingjs/a2s/A2SEvent.js +swingjs/a2s/A2SListener.js +swingjs/a2s/Applet.js +swingjs/a2s/Button.js +swingjs/a2s/Label.js +swingjs/a2s/Panel.js +swingjs/a2s/Scrollbar.js +swingjs/a2s/ScrollPane.js +swingjs/a2s/TextArea.js +swingjs/a2s/TextField.js +swingjs/api/Interface.js +swingjs/api/js/DOMNode.js +swingjs/api/js/HTML5CanvasContext2D.js +swingjs/api/js/JSInterface.js +swingjs/jquery/JQueryUI.js +swingjs/JSApp.js +swingjs/JSAppletThread.js +swingjs/JSAppletViewer.js +swingjs/JSFocusPeer.js +swingjs/JSFontMetrics.js +swingjs/JSFrameViewer.js +swingjs/JSGraphics2D.js +swingjs/JSGraphicsConfiguration.js +swingjs/JSGraphicsEnvironment.js +swingjs/JSImage.js +swingjs/JSImagekit.js +swingjs/JSMouse.js +swingjs/JSNullComponentPeer.js +swingjs/JSScreenDevice.js +swingjs/JSThreadGroup.js +swingjs/JSToolkit.js +swingjs/JSUtil.js +swingjs/plaf/ButtonListener.js +swingjs/plaf/DefaultMenuLayout.js +swingjs/plaf/HTML5LookAndFeel.js +swingjs/plaf/JSAppletUI.js +swingjs/plaf/JSButtonUI.js +swingjs/plaf/JSCheckBoxMenuItemUI.js +swingjs/plaf/JSCheckBoxUI.js +swingjs/plaf/JSComboBoxUI.js +swingjs/plaf/JSComponentUI.js +swingjs/plaf/JSEventHandler.js +swingjs/plaf/JSFrameUI.js +swingjs/plaf/JSGraphicsUtils.js +swingjs/plaf/JSLabelUI.js +swingjs/plaf/JSLayeredPaneUI.js +swingjs/plaf/JSLightweightUI.js +swingjs/plaf/JSMenuBarUI.js +swingjs/plaf/JSMenuItemUI.js +swingjs/plaf/JSMenuUI.js +swingjs/plaf/JSPanelUI.js +swingjs/plaf/JSPopupMenuSeparatorUI.js +swingjs/plaf/JSPopupMenuUI.js +swingjs/plaf/JSRadioButtonMenuItemUI.js +swingjs/plaf/JSRadioButtonUI.js +swingjs/plaf/JSRootPaneUI.js +swingjs/plaf/JSScrollBarUI.js +swingjs/plaf/JSScrollPaneUI.js +swingjs/plaf/JSSeparatorUI.js +swingjs/plaf/JSSliderUI.js +swingjs/plaf/JSTextAreaUI.js +swingjs/plaf/JSTextFieldUI.js +swingjs/plaf/JSTextUI.js +swingjs/plaf/JSTextViewUI.js +swingjs/plaf/JSViewportUI.js +swingjs/plaf/JSWindowUI.js +swingjs/plaf/LazyActionMap.js +swingjs/plaf/Resizer.js +swingjs/plaf/TextListener.js + + diff --git a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/net.sf.j2s.core.jar b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/net.sf.j2s.core.jar new file mode 100644 index 000000000..2fe0400a2 Binary files /dev/null and b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/net.sf.j2s.core.jar differ diff --git a/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/timestamp b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/timestamp new file mode 100644 index 000000000..b36a14d42 --- /dev/null +++ b/sources/net.sf.j2s.core/dist/swingjs/ver/3.2.6/timestamp @@ -0,0 +1 @@ +20200103220342 diff --git a/sources/net.sf.j2s.core/src/net/sf/j2s/core/CorePlugin.java b/sources/net.sf.j2s.core/src/net/sf/j2s/core/CorePlugin.java index 827b12329..f34a2f59d 100644 --- a/sources/net.sf.j2s.core/src/net/sf/j2s/core/CorePlugin.java +++ b/sources/net.sf.j2s.core/src/net/sf/j2s/core/CorePlugin.java @@ -25,7 +25,10 @@ public class CorePlugin extends Plugin { * "net.sf.j2s.core.jar" not "net.sf.j2s.core.3.2.5" * */ - public static String VERSION = "3.2.5-v4"; + public static String VERSION = "3.2.6-v1"; + // BH 2020.01.03 -- 3.2.6-v1 fixes for $__T and some synthetic methods missing + // BH 2020.01.01 -- 3.2.6-v1 fixes for generic varargs with only one parameter + // BH 2019.12.19 -- 3.2.6-v0 adds C$.$fields$ // BH 2019.12.15 -- 3.2.5-v4 fix for local class within anonymous class not getting name // BH 2019.12.12 -- 3.2.5-v3 fix for enums == null in annotations // BH 2019.12.06 -- 3.2.5-v2 fix for try(resources) not closing those diff --git a/sources/net.sf.j2s.core/src/net/sf/j2s/core/Java2ScriptCompiler.java b/sources/net.sf.j2s.core/src/net/sf/j2s/core/Java2ScriptCompiler.java index 6db3b6227..e372015dc 100644 --- a/sources/net.sf.j2s.core/src/net/sf/j2s/core/Java2ScriptCompiler.java +++ b/sources/net.sf.j2s.core/src/net/sf/j2s/core/Java2ScriptCompiler.java @@ -48,6 +48,7 @@ class Java2ScriptCompiler { private final HashSet copyResources = new HashSet(); private Map htMethodsCalled; private List lstMethodsDeclared; + private String ignoredAnnotations; private final static String J2S_COMPILER_STATUS = "j2s.compiler.status"; private final static String J2S_COMPILER_STATUS_ENABLE = "enable"; @@ -90,6 +91,14 @@ class Java2ScriptCompiler { private static final String J2S_EXCLUDED_PATHS = "j2s.excluded.paths"; private static final String J2S_EXCLUDED_PATHS_DEFAULT = ""; + /** + * Added 3.2.6 + */ + private static final String J2S_COMPILER_READ_ANNOTATIONS = "j2s.compiler.read.annotations"; + private static final String J2S_COMPILER_READ_ANNOTATIONS_DEFAULT = "true"; + private static final String J2S_COMPILER_IGNORED_ANNOTATIONS = "j2s.compiler.ignored.annotations"; + private static final String J2S_COMPILER_IGNORED_ANNOTATIONS_DEFAULT = "CallerSensitive;ConstructorProperties;Deprecated;Override;SaveVarargs;SuppressWarnings;"; + private static final String J2S_COMPILER_NONQUALIFIED_PACKAGES = "j2s.compiler.nonqualified.packages"; private static final String J2S_COMPILER_NONQUALIFIED_PACKAGES_DEFAULT = ""; @@ -100,12 +109,12 @@ class Java2ScriptCompiler { private static final String J2S_COMPILER_MODE_DEFAULT = "nodebug"; private static final String J2S_COMPILER_MODE_DEBUG = "debug"; - private static final String J2S_CLASS_REPLACEMENTS = "j2s.class.replacements"; - private static final String J2S_CLASS_REPLACEMENTS_DEFAULT = ""; - private static final String J2S_TEMPLATE_HTML = "j2s.template.html"; private static final String J2S_TEMPLATE_HTML_DEFAULT = "template.html"; + private static final String J2S_CLASS_REPLACEMENTS = "j2s.class.replacements"; + private static final String J2S_CLASS_REPLACEMENTS_DEFAULT = ""; + private Properties props; private String htmlTemplate = null; @@ -283,6 +292,10 @@ boolean initializeProject(IJavaProject project, boolean isCompilationParticipant lstExcludedPaths = null; } + boolean readAnnotations = !"false".equals(getProperty(J2S_COMPILER_READ_ANNOTATIONS, J2S_COMPILER_READ_ANNOTATIONS_DEFAULT)); + + ignoredAnnotations = (readAnnotations ? getProperty(J2S_COMPILER_IGNORED_ANNOTATIONS, J2S_COMPILER_IGNORED_ANNOTATIONS_DEFAULT) : null); + testing = "true".equalsIgnoreCase(getProperty(J2S_TESTING, J2S_TESTING_DEFAULT)); String prop = getProperty(J2S_COMPILER_NONQUALIFIED_PACKAGES, J2S_COMPILER_NONQUALIFIED_PACKAGES_DEFAULT); @@ -308,6 +321,7 @@ boolean initializeProject(IJavaProject project, boolean isCompilationParticipant System.out.println("J2S using HTML template " + file); } + Java2ScriptVisitor.setAnnotating(ignoredAnnotations); Java2ScriptVisitor.setDebugging(isDebugging); Java2ScriptVisitor.setLogging(lstMethodsDeclared, htMethodsCalled, logAllCalls); @@ -315,7 +329,7 @@ boolean initializeProject(IJavaProject project, boolean isCompilationParticipant Java2ScriptVisitor.NameMapper.setClassReplacements(classReplacements); if (isCleanBuild) - Java2ScriptVisitor.clearStringLiteralCache(); + Java2ScriptVisitor.startCleanBuild(); } catch (Exception e) { System.out.println("error " + e + " " + e.getStackTrace()); diff --git a/sources/net.sf.j2s.core/src/net/sf/j2s/core/Java2ScriptVisitor.java b/sources/net.sf.j2s.core/src/net/sf/j2s/core/Java2ScriptVisitor.java index ad5c7d421..7791bfb9c 100644 --- a/sources/net.sf.j2s.core/src/net/sf/j2s/core/Java2ScriptVisitor.java +++ b/sources/net.sf.j2s.core/src/net/sf/j2s/core/Java2ScriptVisitor.java @@ -17,12 +17,10 @@ import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Stack; -import java.util.function.Consumer; -import java.util.function.IntConsumer; import java.util.regex.Pattern; import org.eclipse.jdt.core.IJavaProject; @@ -159,6 +157,9 @@ // // todo: j2sdoc in static field showing up in default static block only, not in initializer block. +// BH 2020.01.03 -- 3.2.6-v1 fixes for $__T and some synthetic methods missing +// BH 2020.01.01 3.2.6-v1 fixes for generic varargs with only one parameter +// BH 2019.12.19 3.2.6-v0 C$.$clinit$=2 adds C$.$fields$, Clazz._getFields // BH 2019.11.20 3.2.5-v1 fix and refactoring for FINAL $finals$ fix throughout java.util.stream // NOTE: All of the original (complicated and only partially working) nested block-counting code in @@ -227,6 +228,36 @@ */ public class Java2ScriptVisitor extends ASTVisitor { + // 3.2.4: + // C$.$clinit$ -- runs Clazz.load(cl,1) for getting dependencies + // C$ static defaults included in class loading code + // C$.$init0$ -- object defaults from within Clazz.newInstance, before any constructors + // C$.$init$ -- object declarations from the constructor, just after any super() call or whenever there is no this() call + // C$.__ANN__ [[[...]]] annotation references, without types + // no @interface + // no field value typing + // no member return value typing + + // 3.2.5: + // C$.$clinit$ = 1 -- tells Clazz to use C$.$statics$ + // C$ static defaults included in class loading code + // C$.$static$ -- static declarations; once only; processed later by Clazz.load(cl,2) + // C$.$init0$ -- object defaults from within Clazz.newInstance, before any constructors + // C$.$init$ -- object declarations from the constructor, just after any super() call or whenever there is no this() call + // C$.__ANN__ [[[...]]] annotation references without types + // no @interface + // no field value typing + // no member return value typing + + // 3.2.6: + // C$.$clinit$ = 2 tells Clazz to use $fields$ + // C$.$fields$ = [[static][object]];referenced by Clazz.getFields() + // C$.$init$ -- object declarations from the constructor, just after any super() call or whenever there is no this() call + // C$.$static$ and C$.$init0$ created dynamically + // C$.$getMembers$ -- for @interface + // C$.$getAnn$ -- return annotation references, with types + // java.lang.Class can use C$.$fields$ to get field types for reflection + static final String VERSION = CorePlugin.VERSION; private static final int NOT_LAMBDA = 0; @@ -243,6 +274,8 @@ public class Java2ScriptVisitor extends ASTVisitor { private final static int METHOD_ISQUALIFIED = 16; private final static int METHOD_NULLEXPRESSION = 32; + private final static int METHOD_ALIAS = 64; + private static final int NOT_LOCAL = 0; private static final int REALLY_LOCAL_CLASS = 1; private static final int ANON_CLASS = 2; @@ -254,30 +287,82 @@ public class Java2ScriptVisitor extends ASTVisitor { private static final String NULL_PACKAGE = "_"; - static final int ANNOTATION_TYPE_UNKNOWN = -1; - final static int JAXB_TYPE_NONE = 0; - final static int JAXB_TYPE_FIELD = 1; - final static int JAXB_TYPE_PUBLIC_MEMBER = 2; - final static int JAXB_TYPE_PROPERTY = 3; - static final int JAXB_TYPE_ENUM = 4; + private final static int CHECK_J2S_IGNORE_ONLY = 1; + private final static int CHECK_ANNOTATIONS_ONLY = 2; + private final static int CHECK_J2S_IGNORE_AND_ANNOTATIONS = 3; + + static final int ANNOTATION_TYPE_UNKNOWN = 0; + final static int JAXB_TYPE_NONE = 1; + final static int JAXB_TYPE_FIELD = 2; + final static int JAXB_TYPE_PUBLIC_MEMBER = 3; + final static int JAXB_TYPE_PROPERTY = 4; + static final int JAXB_TYPE_ENUM = 5; + static final int JAXB_TYPE_UNSPECIFIED = 6; + // UNSPECIFIED indicates that some @Xml... annotation was used, but + // no XMLAccesorType was indicated for the class, so + // this must be determined at run time based on the package. In this case we + // introduce the !XMLPublic(true|false) annotation into C$.__ANN__ so that at + // least we know whether this field is public or not. This is then a flag to the + // marshaller to filter fields based on whether we are using PUBLIC_MEMBER or + // not from the package or superclass. Could work. Not tested. + + // The main problem here is that we don't necessarily know if this is JAXB or not - static final int TEST_TYPE = 10; + static final int NOT_JAXB = 0x10; + + static Map htClassReplacements; + static List lstPackageReplacements; + + + private static List global_lstMethodsDeclared; + private static Map global_htMethodsCalled; + private static boolean global_logAllCalls; + + private static Map>> syntheticClassMethodNameMap = new HashMap>>(); + private static Map> genericClassTypes = new HashMap>(); + + private static Map htStrLitCache = new Hashtable<>(); /** - * UNSPECIFIED indicates that no XMLAccesorType was indicated for the class, so - * this must be determined at run time based on the package. In this case we - * introduce the !XMLPublic(true|false) annotation into C$.__ANN__ so that at - * least we know whether this field is public or not. This is then a flag to the - * marshaller to filter fields based on whether we are using PUBLIC_MEMBER or - * not from the package or superclass. Could work. Not tested. + * includes @j2sDebug blocks; from j2s.compiler.mode=debug in .j2s + * */ - static final int JAXB_TYPE_UNSPECIFIED = 5; + static boolean global_j2sFlag_isDebugging = false; + + /** + * list of annotations to ignore or null to ignore ALL + * + */ + private static String global_ignoredAnnotations = ";" + + "CallerSensitive;" + + "ConstructorProperties;" + + "Deprecated;" + + "Override;" + + "SaveVarargs;" + + "SuppressWarnings;" + ; + + + public static void setDebugging(boolean isDebugging) { + global_j2sFlag_isDebugging = isDebugging; + } + + + public static void setAnnotating(String ignoredAnnotations) { + global_ignoredAnnotations = (ignoredAnnotations == null ? null : ";" + ignoredAnnotations + ";"); + } + + static void startCleanBuild() { + htStrLitCache = new Hashtable<>(); + } + /** * annotations collected for a class */ private List class_annotations; private int class_annotationType = ANNOTATION_TYPE_UNKNOWN; +// private boolean class_hasTypeAnnotations; @@ -413,14 +498,132 @@ public String getMyPackageName() { /** * Buffer that keeps all compiled *.js. */ - private StringBuffer buffer = new StringBuffer(); + StringBuffer buffer = new StringBuffer(); private char getLastCharInBuffer() { return (buffer.length() == 0 ? '\0' : buffer.charAt(buffer.length() - 1)); } - private StringBuffer init0Buffer; + private final static int FIELD_INFO_OBJECT = 0; + private final static int FIELD_INFO_STATIC = 1; + private final static int FIELD_BOOLEAN = 0; + private final static int FIELD_BYTE = 1; + private final static int FIELD_CHAR = 2; + private final static int FIELD_DOUBLE = 3; + private final static int FIELD_FLOAT = 4; + private final static int FIELD_INT = 5; + private final static int FIELD_LONG = 6; + private final static int FIELD_SHORT = 7; + private final static int FIELD_STRING = 8; + private final static int FIELD_OTHER = 9; + private final static int FIELD_COUNT = 10; + + // order above can be changed, but typeCode order must then be adapted + + private final static String typeCodes = "ZBCDFILHSO"; + + private class FieldInfo { + @SuppressWarnings("unchecked") + private List[][] fields = new ArrayList[2][FIELD_COUNT]; + + private String lastType = null; + + FieldInfo() { + } + + void add$fields$() { + StringBuffer buf = buffer; + buf.append("\nC$.$fields$=["); + for (int i = 0; i < 2; i++) { + List[] fieldData = fields[i]; + int lastType = -1; + for (lastType = FIELD_COUNT; --lastType >= 0;) + if (fieldData[lastType] != null) + break; + // Skip statics if there are none + if (i == 1 && lastType < 0) + break; + buf.append(i == 0 ? "[" : "\n,["); + String sep = "'"; + for (int j = 0; j <= lastType; j++) { + List list = fieldData[j]; + if (list == null) + continue; + buf.append(sep).append(typeCodes.charAt(j)).append("',["); + for (int k = 0; k < list.size(); k++) { + buf.append(k == 0 ? "'" : ",'").append(list.get(k)).append("'"); + } + sep = ",'"; + buf.append("]"); + } + buf.append("]"); + } + buf.append("]\n"); + +// if (init0Buffer.length() > 0) { +// String buf = buffer.substring(len); +// buffer.setLength(len); +// buffer.append("\nClazz.newMeth(C$, '$init0$', function () {\n"); +// buffer.append("var c;if((c = C$.superclazz) && (c = c.$init0$))c.apply(this);\n"); +// buffer.append(init0Buffer); +// buffer.append("}, 1);\n"); +// buffer.append(buf); +// } + } + + @SuppressWarnings("null") + void addField(boolean isStatic, String name, IVariableBinding var, int tpt) { + int fpt = (isStatic ? FIELD_INFO_STATIC : FIELD_INFO_OBJECT); + List[] fieldData = fields[fpt]; + String typeName = null; + if (tpt < 0) { + typeName = j2sNonPrimitiveName(var.getType(), false); + tpt = (typeName.equals("String") ? FIELD_STRING : FIELD_OTHER); + } + List lst = fieldData[tpt]; + if (lst == null) + lst = fieldData[tpt] = new ArrayList(); + if (tpt == FIELD_OTHER) { + if (typeName.equals(lastType)) { + lst.add("+" + name); + } else { + lst.add(name); + lst.add(lastType = typeName); + } + } else { + lst.add(name); + } + } + + int getPrimitiveDefaultType(Code code) { + switch (code.toString()) { + case "boolean": + return FIELD_BOOLEAN; + case "byte": + return FIELD_BYTE; + case "char": + return FIELD_CHAR; + case "double": + return FIELD_DOUBLE; + case "float": + return FIELD_FLOAT; + case "int": + return FIELD_INT; + case "long": + return FIELD_LONG; + case "short": + return FIELD_SHORT; + default: + return -1; + } + } + + + } + private FieldInfo fieldInfo; + private boolean haveFields; + private ArrayList applets, apps; private void addApplication() { @@ -464,7 +667,7 @@ public boolean visit(CompilationUnit node) { public boolean visit(PackageDeclaration node) { setMapJavaDoc(node); - List annotations = node.annotations(); + List annotations = node.annotations(); if (annotations != null && annotations.size() > 0) { for (int i = 0; i < annotations.size(); i++) addAnnotation((Annotation) annotations.get(i), node, CHECK_ANNOTATIONS_ONLY); @@ -503,7 +706,7 @@ private void setPackage(String packageName) { } else { buffer.append("Clazz.newPackage(\"").append(packageName).append("\")"); } - buffer.append(",I$=[];\r\n"); + buffer.append(",I$=[];\n"); } public boolean visit(AssertStatement node) { @@ -514,7 +717,7 @@ public boolean visit(AssertStatement node) { buffer.append("}, function(){return "); msg.accept(this); } - buffer.append("});\r\n"); + buffer.append("});\n"); trailingBuffer.hasAssert = true; return false; } @@ -525,7 +728,7 @@ public boolean visit(AssertStatement node) { */ public boolean visit(Block node) { - buffer.append("{\r\n"); + buffer.append("{\n"); ASTNode parent = node.getParent(); if (parent instanceof MethodDeclaration && !((MethodDeclaration) parent).isConstructor() || parent instanceof Initializer) { @@ -659,7 +862,22 @@ private void openNew( String javaClassName, String anonJavaName, IMethodBinding constructorMethodBinding, int lambdaArity) { - buffer.append("Clazz.new_("); + + + buffer.append("Clazz.new_("); + if (javaClass.isParameterizedType()) { + Iterator map = getGenericClassTypes(javaClass).keySet().iterator(); + ITypeBinding[] args = javaClass.getTypeArguments(); + buffer.append("1,{"); + String sep = ""; + for (int i = 0; i < args.length; i++) { + buffer.append(sep).append(map.next()).append(":\"").append(j2sNonPrimitiveName(args[i], true)).append("\""); + sep = ","; + } + buffer.append("},"); + } + + String finalQualifiedName; if (anonJavaName == null) { // not inner @@ -676,7 +894,7 @@ private void openNew( return; } String qName = getFinalMethodNameWith$Params(finalQualifiedName + ".c$", null, constructorMethodBinding, null, - false, lambdaArity >= 0 ? METHOD_LAMBDA_C : METHOD_NOTSPECIAL); + false, null, lambdaArity >= 0 ? METHOD_LAMBDA_C : METHOD_NOTSPECIAL); // if no parameters, we just give the name of the class, not the constructor buffer.append(qName.endsWith(".c$") ? finalQualifiedName : qName); } @@ -768,7 +986,12 @@ private String processLocalInstance(ASTNode node, ASTNode anonymousClassDeclarat class_localType = oldLocalType; if (lambdaType != LAMBDA_METHOD && localType != REALLY_LOCAL_CLASS) buffer.append(")"); // end of line (..., ...) - return (isStatic && package_htClassKeyToVisitedFinalVars.get(binding.getKey()).isEmpty() ? anonName : null); + if (!isStatic) + return null; + + String key= binding.getKey(); + Set set = package_htClassKeyToVisitedFinalVars.get(key); + return (set == null || set.isEmpty() ? anonName : null); } /** @@ -853,12 +1076,12 @@ private String listFinalVariables(Set visitedVars, boolean all public boolean visit(ConstructorInvocation node) { IMethodBinding constructorBinding = node.resolveConstructorBinding(); List arguments = node.arguments(); - buffer.append(getFinalMethodNameWith$Params("C$.c$", null, constructorBinding, null, false, METHOD_NOTSPECIAL)) + buffer.append(getFinalMethodNameWith$Params("C$.c$", null, constructorBinding, null, false, null, METHOD_NOTSPECIAL)) .append(".apply(this"); IMethodBinding methodDeclaration = (constructorBinding == null ? null : constructorBinding.getMethodDeclaration()); addMethodParameterList(arguments, methodDeclaration, ", [", "]", METHOD_CONSTRUCTOR); - buffer.append(");\r\n"); + buffer.append(");\n"); return false; } @@ -881,7 +1104,7 @@ private void addLabel(SimpleName label, boolean isDefining) { buffer.append(' '); buffer.append(NameMapper.getJavaScriptCollisionIdentifier(label.getIdentifier(), true)); } - buffer.append(isDefining ? " : " : ";\r\n"); + buffer.append(isDefining ? " : " : ";\n"); } public boolean visit(DoStatement node) { @@ -889,7 +1112,7 @@ public boolean visit(DoStatement node) { node.getBody().accept(this); buffer.append(" while ("); node.getExpression().accept(this); - buffer.append(");\r\n"); + buffer.append(");\n"); return false; } @@ -923,7 +1146,7 @@ public boolean visit(EnhancedForStatement node) { } buffer.append(") "); node.getBody().accept(this); - buffer.append("\r\n"); + buffer.append("\n"); return false; } @@ -981,7 +1204,7 @@ public boolean visit(ExpressionStatement node) { } public void endVisit(ExpressionStatement node) { - buffer.append(";\r\n"); + buffer.append(";\n"); } @SuppressWarnings("unchecked") @@ -998,7 +1221,7 @@ public boolean visit(ForStatement node) { visitList(node.updaters(), ", "); buffer.append(") "); node.getBody().accept(this); - buffer.append("\r\n"); + buffer.append("\n"); return false; } @@ -1039,13 +1262,14 @@ public boolean visit(MethodDeclaration node) { return false; } - private final static int METHOD_FULLY_QUALIFIED = 0; private final static int METHOD_$_QUALIFIED = 1; private final static int METHOD_UNQUALIFIED = 2; + private final static int METHOD_FULLY_QUALIFIED = 4; + private final static int METHOD_FULLY_QUALIFIED_JUST_ONE = 12; /** * Called by visit(MethodDeclaration) as well as addLambdaMethod(). - * + * @param mnode TODO * @param mBinding * @param parameters * @param body @@ -1053,8 +1277,9 @@ public boolean visit(MethodDeclaration node) { * @param lambdaType */ @SuppressWarnings("null") - private void processMethodDeclaration(IMethodBinding mBinding, List parameters, ASTNode body, - boolean isConstructor, int lambdaType) { + private void processMethodDeclaration(MethodDeclaration mnode, IMethodBinding mBinding, List parameters, + ASTNode body, boolean isConstructor, int lambdaType) { + String aliases = (mnode == null ? null : checkJ2SMethodDoc(mnode)); int mods = mBinding.getModifiers(); boolean isNative = Modifier.isNative(mods); if (body == null && !isNative && lambdaType == NOT_LAMBDA) { @@ -1068,7 +1293,7 @@ private void processMethodDeclaration(IMethodBinding mBinding, List par : temp_add$UnqualifiedMethod ? METHOD_$_QUALIFIED : METHOD_FULLY_QUALIFIED); if (isUserApplet && lambdaType == NOT_LAMBDA && !isConstructor && !isStatic && isPublic) qualification |= METHOD_UNQUALIFIED; - String finalName = getFinalMethodNameOrArrayForDeclaration(mBinding, isConstructor, qualification); + String finalName = getFinalMethodNameOrArrayForDeclaration(mBinding, isConstructor, aliases, qualification); boolean isMain = (isStatic && isPublic && mBinding.getName().equals("main") && mBinding.getKey().indexOf(";.main([Ljava/lang/String;)V") >= 0); @@ -1083,7 +1308,7 @@ private void processMethodDeclaration(IMethodBinding mBinding, List par && (finalName.equals("'c$'") || mBinding.isVarargs() && mBinding.getParameterTypes().length == 1)) class_haveDefaultConstructor = true; // in case we are not qualifying // names here - buffer.append("\r\nClazz.newMeth(C$, ").append(finalName).append(", function ("); + buffer.append("\nClazz.newMeth(C$, ").append(finalName).append(", function ("); if (parameters == null) // lambda method buffer.append(getLambdaParamList(mBinding, -1)); @@ -1100,7 +1325,7 @@ private void processMethodDeclaration(IMethodBinding mBinding, List par ASTNode firstStatement; if (statements.size() == 0 || !((firstStatement = statements.get(0)) instanceof SuperConstructorInvocation) && !(firstStatement instanceof ConstructorInvocation)) { - buffer.append("{\r\n"); + buffer.append("{\n"); String superclassName = getJavaClassNameSuperNoBrackets(mClass); if (superclassName == null) { addCallInit(); @@ -1114,12 +1339,12 @@ private void processMethodDeclaration(IMethodBinding mBinding, List par } } else if (body == null) { // not a constructor and no body -- native - buffer.append("{\r\n"); + buffer.append("{\n"); if (isNative) { - buffer.append("alert('native method must be replaced! " + mBinding.getName() + "');\r\n"); + buffer.append("alert('native method must be replaced! " + mBinding.getName() + "');\n"); log("native: " + mBinding.getName()); } - buffer.append("}\r\n"); + buffer.append("}\n"); } else { body.accept(this); } @@ -1127,7 +1352,7 @@ private void processMethodDeclaration(IMethodBinding mBinding, List par buffer.append(", ").append(isNative ? 2 : 1); else if (isPrivate) buffer.append(", " + getPrivateVar(mClass, false)); - buffer.append(");\r\n"); + buffer.append(");\n"); } /** @@ -1150,9 +1375,7 @@ private static boolean isFinalOrEffectivelyFinal(IBinding binding) { } public boolean visit(MethodInvocation node) { - Expression expression = node.getExpression(); - IMethodBinding mBinding = node.resolveMethodBinding(); - addMethodInvocation(node.getName(), node.arguments(), mBinding, expression, -1); + addMethodInvocation(node.getName(), node.arguments(), node.resolveMethodBinding(), node.getExpression(), -1); return false; } @@ -1172,6 +1395,9 @@ private boolean addMethodInvocation(SimpleName javaQualifier, List arguments, } String methodName = mBinding.getName(); ITypeBinding declaringClass = mBinding.getDeclaringClass(); + if (mBinding.isSynthetic()) { + System.out.println(">>>synthetic invoc " + methodName + " " + mBinding); + } boolean isStatic = isStatic(mBinding); boolean isPrivate = isPrivate(mBinding); boolean isPrivateAndNotStatic = isPrivate && !isStatic; @@ -1198,7 +1424,6 @@ private boolean addMethodInvocation(SimpleName javaQualifier, List arguments, buffer.append(bname); buffer.append("."); } else if (!isStatic) { - //bufferDebug("simplenamemethodinvoc " + class_localType); buffer.append("this."); } else { // this will be C$., I think. @@ -1254,7 +1479,7 @@ private boolean addMethodInvocation(SimpleName javaQualifier, List arguments, | (lambdaArity >= 0 ? LAMBDA_METHOD : 0) | (isStatic ? FINAL_STATIC : 0)); String finalMethodNameWith$Params = getFinalMethodNameWith$Params(j2sName, declaringClassJavaClassName, - mBinding, null, true, METHOD_NOTSPECIAL); + mBinding, null, true, null, METHOD_NOTSPECIAL); if (lambdaArity >= 0) { // The problem here is that we cannot apply a method from an interface @@ -1271,12 +1496,6 @@ private boolean addMethodInvocation(SimpleName javaQualifier, List arguments, || "java.lang.Class".equals(removeBracketsAndFixNullPackageName(declaringClassJavaClassName)) // String.class::cast ) // BH Added 2019.05.13 && lambdaArity == mBinding.getParameterTypes().length)); - -//bufferDebug("addmeth isclasstarget " + isStatic + " classIsTarget="+ classIsTarget -// + " ivarbinding=" +isVariableBinding(expression) + " exp=" + (expression == null ? -// null : expression.getClass().getName()) -// + " " + declaringClassJavaClassName -// ); String opening = (classIsTarget ? "$$." : "t.") + finalMethodNameWith$Params + ".apply(" + (isStatic ? "null" : classIsTarget ? "$$" : "t") + ",["; buffer.append(opening); @@ -1337,9 +1556,6 @@ private void appendFinalMethodQualifier(Expression qualifier, ITypeBinding decla if ((flags & FINAL_LAMBDA) != 0) { buffer.append(getFinalJ2SClassNameQualifier(null, declaringClass, null, flags)); } else { - - ////bufferDebug("this - method " + class_localType + " " + flags); - buffer.append(bname == null ? "this" : bname); } } else if (qualifier instanceof Name && !isVariableBinding(qualifier)) { @@ -1374,7 +1590,7 @@ public boolean visit(ReturnStatement node) { ITypeBinding retType = (mBinding == null ? null : mBinding.getReturnType()); addExpressionAsTargetType(expression, retType, "r", null); } - buffer.append(";\r\n"); + buffer.append(";\n"); return false; } @@ -1397,21 +1613,26 @@ public boolean visit(SuperConstructorInvocation node) { } public boolean visit(SuperMethodInvocation node) { + boolean isQualified = (node.getQualifier() != null);//for this.: && class_localType != LAMBDA_EXPRESSION) { IMethodBinding mBinding = node.resolveMethodBinding(); String finalMethodNameWith$Params = getFinalMethodNameWith$Params(null, null, mBinding, null, false, - METHOD_NOTSPECIAL); + null, METHOD_NOTSPECIAL); // BH if this is a call to super.clone() and there is no superclass, or // the superclass is Object, // then we need to invoke Clazz.clone(this) directly instead of calling // C$.superclazz.clone() if ("clone$".equals(finalMethodNameWith$Params) && getJavaClassSuper(mBinding.getDeclaringClass()) == null) { buffer.append("Clazz.clone(this)"); + return false; + } + if (isQualified) { + node.getQualifier().accept(this); } else { - buffer.append("C$.superclazz.prototype." + finalMethodNameWith$Params + ".apply(this, "); - buffer.append("["); - addMethodParameterList(node.arguments(), mBinding, null, null, METHOD_NOTSPECIAL); - buffer.append("])"); + buffer.append("C$.superclazz"); } + buffer.append(".prototype." + finalMethodNameWith$Params + ".apply(this, ["); + addMethodParameterList(node.arguments(), mBinding, null, null, METHOD_NOTSPECIAL); + buffer.append("])"); return false; } @@ -1420,9 +1641,9 @@ public boolean visit(SuperMethodInvocation node) { public boolean visit(SwitchStatement node) { buffer.append("switch ("); addNonCharacter(node.getExpression()); - buffer.append(") {\r\n"); + buffer.append(") {\n"); visitList(node.statements(), ""); - buffer.append("}\r\n"); + buffer.append("}\n"); return false; } @@ -1433,7 +1654,7 @@ public boolean visit(SwitchCase node) { buffer.append("case "); addNonCharacter(node.getExpression()); } - buffer.append(":\r\n"); + buffer.append(":\n"); return false; } @@ -1455,7 +1676,7 @@ public boolean visit(SynchronizedStatement node) { if (!(e instanceof MethodInvocation && expr.indexOf(".getTreeLock()") >= 0)) { buffer.append("("); buffer.append(expr); - buffer.append(");\r\n"); + buffer.append(");\n"); } } node.getBody().accept(this); @@ -1465,7 +1686,7 @@ public boolean visit(SynchronizedStatement node) { public boolean visit(ThrowStatement node) { buffer.append("throw "); node.getExpression().accept(this); - buffer.append(";\r\n"); + buffer.append(";\n"); return false; } @@ -1481,7 +1702,7 @@ public boolean visit(TryStatement node) { // A resource is either a VariableDeclarationExpression or (since JLS9) a Name. int pt = -1; if (resources != null && resources.size() > 0 ) { - buffer.append("try {\r\n"); + buffer.append("try {\n"); pt = buffer.length(); } buffer.append(size > 0 || finallyBlock != null ? "try " : "/*try*/ "); @@ -1512,7 +1733,7 @@ public boolean visit(TryStatement node) { endedWithThrowable = true; } else { if (!scopeAdded) { - buffer.append("{\r\n"); + buffer.append("{\n"); scopeAdded = true; } buffer.append(haveType ? " || " : "if ("); @@ -1528,22 +1749,22 @@ public boolean visit(TryStatement node) { String eName = exName.getIdentifier(); boolean notEName = false; if (!catchEName.equals(eName)) { - buffer.append("{\r\nvar " + eName + " = " + catchEName + ";\r\n"); + buffer.append("{\nvar " + eName + " = " + catchEName + ";\n"); notEName = true; } element.getBody().accept(this); if (notEName) { - buffer.append("\r\n}"); + buffer.append("\n}"); } if (iter.hasNext()) { buffer.append(" else "); } } if (!endedWithThrowable) { - buffer.append(" else {\r\nthrow " + catchEName + ";\r\n}"); + buffer.append(" else {\nthrow " + catchEName + ";\n}"); } if (scopeAdded) { - buffer.append("\r\n}"); + buffer.append("\n}"); } } if (finallyBlock != null) { @@ -1559,13 +1780,13 @@ public boolean visit(TryStatement node) { ASTNode resource = resources.get(i); pt = buffer.length(); resource.accept(this); - buffer.append(";\r\n"); + buffer.append(";\n"); closing = getResourceClosing(pt) + closing; } buffer.append(buf); - buffer.append("\r\n}finally{/*res*/").append(closing).append("}"); + buffer.append("\n}finally{/*res*/").append(closing).append("}"); } - buffer.append("\r\n"); + buffer.append("\n"); return false; } @@ -1578,7 +1799,7 @@ private String getResourceClosing(int pt) { } if (name.startsWith("var ")) name = name.substring(4); - return "\r\ntry{" + name + "&&" + name + ".close$&&"+ name + ".close$()}catch(_){}"; + return "\ntry{" + name + "&&" + name + ".close$&&"+ name + ".close$()}catch(_){}"; } /** @@ -1612,7 +1833,7 @@ public boolean visit(VariableDeclarationStatement node) { buffer.append("var "); ASTNode next = iter.next(); next.accept(this); - buffer.append(";\r\n"); + buffer.append(";\n"); } return false; } @@ -1623,19 +1844,15 @@ public boolean visit(WhileStatement node) { node.getExpression().accept(this); buffer.append(")"); node.getBody().accept(this); - buffer.append("\r\n"); + buffer.append("\n"); return false; } ////////// END visit/endVisit /////////// - private static final int FIELD_DECL_STATIC_NONDEFAULT = 1; - private static final int FIELD_DECL_STATIC_DEFAULTS = 2; - private static final int FIELD_DECL_NONSTATIC_ALL = 3; - private void addAnonymousFunctionWrapper(boolean isOpen) { buffer.append( - isOpen ? (buffer.lastIndexOf(")") >= buffer.length() - 3 ? ";" : "") + "\r\n(function(){" : "})()\r\n"); + isOpen ? (buffer.lastIndexOf(")") >= buffer.length() - 3 ? ";" : "") + "\n(function(){" : "})()\n"); } /** @@ -1651,11 +1868,11 @@ private void addAnonymousFunctionWrapper(boolean isOpen) { * */ private void addCallInit() { - buffer.append(";C$.$init$.apply(this);\r\n"); + buffer.append(";C$.$init$.apply(this);\n"); } private void appendClinit() { - buffer.append("\r\nC$.$clinit$=1;\r\n"); + buffer.append("\nC$.$clinit$=2;\n"); } /** @@ -1668,9 +1885,13 @@ private void appendClinit() { * @param node * @param binding * @param BodyDeclarations - * @param type 'a' (anonymous class), 'e' (Enum), 'i' (Interface), - * 'l' (local), 'm' (LambdaExpression), or 'c' (standard - * class) + * @param type 'a' (anonymous class), + * 'e' (Enum), + * 'i' (Interface), + * 'l' (local), + * 'm' (LambdaExpression), + * '@' (_at_interface AnnotationType), + * or 'c' (standard class) * @return localName */ @SuppressWarnings({ "null", "unchecked" }) @@ -1684,6 +1905,7 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List boolean isEnum = (type == 'e'); boolean isInterface = (type == 'i'); + boolean isAnnotation = (type == '@'); boolean isTrulyLocal = (type == 'l'); boolean isLambda = (type == 'm'); boolean isAnonymous = (type == 'a' || isLambda); @@ -1744,6 +1966,7 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List } ITypeBinding oldBinding = null; String oldShortClassName = null, this$0Name0 = null, finalShortClassName, finalPackageName; + List oldAnnotations = null; if (isTopLevel) { String javaName = binding.getName(); appendElementKey(javaName); @@ -1754,6 +1977,8 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List if (isAnonymous) { oldShortClassName = class_shortName; oldBinding = class_typeBinding; + oldAnnotations = class_annotations; + class_annotations = null; // anonymous classes reference their package, not their outer class in // Clazz.newClass, so clazz.$this$0 is not assigned. this$0Name0 = this$0Name; @@ -1767,12 +1992,9 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List setClassAndBinding(finalShortClassName, binding); if (isLambda) { buffer.append("("); - - + // problem here 2019.12.07 cifbinary was that $$-wrapped lambda methods must NOT be reused. - //bufferDebug("addcoi " + isLambda + " " + class_localType); - } buffer.append("("); if (!isLambda || class_localType != LAMBDA_WRAPPED) @@ -1827,6 +2049,7 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List buffer.append(", "); List unqualifiedMethods = getUnqualifiedMethods(binding, null); List innerClasses = new ArrayList<>(); + String innerTypes = ""; if (isAnonymous) { if (!(parent instanceof EnumConstantDeclaration)) func = "function(){Clazz.newInstance(this, arguments[0],1,C$);}"; @@ -1846,13 +2069,17 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List } else { for (Iterator iter = bodyDeclarations.iterator(); iter.hasNext();) { BodyDeclaration bd = (BodyDeclaration) iter.next(); - if (bd instanceof TypeDeclaration || bd instanceof EnumDeclaration) { + if (bd instanceof AbstractTypeDeclaration) { + //TypeDeclaration || EnumDeclaration || AnnotationTypeDeclaration) + ITypeBinding b = ((AbstractTypeDeclaration)bd).resolveBinding(); + String s = j2sNonPrimitiveName(b, false); + innerTypes += ",['" + s.substring(s.lastIndexOf(".") + 1) + "'," + b.getModifiers() + "]"; innerClasses.add((AbstractTypeDeclaration) bd); } } if (!isTopLevel || !innerClasses.isEmpty()) { func = null; - buffer.append("function(){\r\n"); + buffer.append("function(){\n"); // add all inner classes iteratively @@ -1869,13 +2096,13 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List // directly by the user using new Foo() if (!isInterface) { buffer.append("Clazz.newInstance(this, arguments") - .append(isTopLevel ? ",0" : "[0]," + !isStatic(binding)).append(",C$);\r\n"); + .append(isTopLevel ? ",0" : "[0]," + !isStatic(binding)).append(",C$);\n"); } buffer.append("}"); } if (isEnum) { superInterfaceTypes = ((EnumDeclaration) node).superInterfaceTypes(); - } else { + } else if (!isAnnotation) { superInterfaceTypes = ((TypeDeclaration) node).superInterfaceTypes(); } @@ -1945,6 +2172,9 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List sep = ", "; } buffer.append(term); + } else if (isAnnotation) { + hasDependents = true; + buffer.append(", 'java.lang.annotation.Annotation'"); } else { buffer.append(", null"); } @@ -1964,7 +2194,11 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List // close the initializer - buffer.append(");\r\n"); + buffer.append(");\n"); + + if (innerTypes.length() > 0) { + buffer.append("C$.$classes$=[" + innerTypes.substring(1) + "];\n"); + } // add the Java8 compatibility local variable $o$ @@ -1976,6 +2210,13 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List TrailingBuffer oldTrailingBuffer = trailingBuffer; trailingBuffer = new TrailingBuffer(); + // for annotations: + boolean haveFieldMethodAnnotations = false; + + FieldInfo fieldInfoOld = fieldInfo; + boolean haveFieldInfoOld = haveFields; + fieldInfo = new FieldInfo(); + /** * the static initializer */ @@ -1985,20 +2226,23 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List for (Iterator iter = bodyDeclarations.iterator(); iter.hasNext();) { BodyDeclaration element = (BodyDeclaration) iter.next(); boolean isField = element instanceof FieldDeclaration; + boolean isMethod = element instanceof MethodDeclaration; + IMethodBinding b = null; + if (!haveFieldMethodAnnotations && isMethod + && (b = ((MethodDeclaration) element).resolveBinding()) != null && b.getAnnotations().length > 0) { + haveFieldMethodAnnotations = true; + } if (isField || element instanceof Initializer) { if ((isInterface || isStatic(element)) && checkAnnotations(element, CHECK_J2S_IGNORE_ONLY)) { lstStatic.add(element); - if (isField) - addFieldDeclaration((FieldDeclaration) element, FIELD_DECL_STATIC_DEFAULTS); } } } } - // for annotations: List enums = (isEnum ? new ArrayList<>() : null); - List fields = (isInterface || isLambda || isEnum ? null : new ArrayList<>()); - List methods = (fields == null ? null : new ArrayList<>()); + List fields = (haveFieldMethodAnnotations || isInterface || isLambda || isEnum ? null : new ArrayList<>()); + List methods = (haveFieldMethodAnnotations || isAnnotation || fields != null ? new ArrayList<>() : null); if (hasDependents) { // Add the class static initializer C$.$clinit$(), which in SwingJS will trigger @@ -2031,7 +2275,7 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List // in Class A. int pt = buffer.length(); - buffer.append("\r\nC$.$static$ = function() {C$.$static$=0;\r\n"); + buffer.append("\nC$.$static$ = function() {C$.$static$=0;\n"); boolean haveDeclarations = isEnum; if (isEnum) addEnumConstants((EnumDeclaration) node, enums); @@ -2039,14 +2283,14 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List BodyDeclaration element = lstStatic.remove(0); if (element instanceof Initializer) { ((Initializer) element).getBody().accept(this); - buffer.append(";\r\n"); + buffer.append(";\n"); haveDeclarations = true; - } else if (addFieldDeclaration((FieldDeclaration) element, FIELD_DECL_STATIC_NONDEFAULT)) { + } else if (addFieldDeclaration((FieldDeclaration) element, true)) { haveDeclarations = true; } } if (haveDeclarations) - buffer.append("}\r\n"); + buffer.append("}\n"); else buffer.setLength(pt); } @@ -2057,13 +2301,10 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List if (!isInterface) { - // if this is not an interface, generate $init0$ and $init$ methods - - StringBuffer init0bufold = init0Buffer; - init0Buffer = new StringBuffer(); + // if this is not an interface, generate $init$ method, which declares nonstatic fields that have initializers and + // is called just after the call to a superconstructor. - int len = buffer.length(); - buffer.append("\r\nClazz.newMeth(C$, '$init$', function () {\r\n"); // C$.$load$&&Clazz.load(C$,2); + buffer.append("\nClazz.newMeth(C$, '$init$', function () {\n"); // C$.$load$&&Clazz.load(C$,2); // we include all field definitions here and all nonstatic // initializers @@ -2074,30 +2315,27 @@ private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List if ((isField || element instanceof Initializer) && !isStatic(element) && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { if (isField) { + addFieldDeclaration((FieldDeclaration) element, false); + if (class_annotations != null && fields == null) + fields = new ArrayList<>(); if (fields != null && !Modifier.isTransient(((FieldDeclaration) element).getModifiers())) fields.add((FieldDeclaration) element); - addFieldDeclaration((FieldDeclaration) element, FIELD_DECL_NONSTATIC_ALL); } else { ((Initializer) element).getBody().accept(this); - buffer.append("\r\n"); + buffer.append("\n"); } } } - buffer.append("}, 1);\r\n"); - - if (init0Buffer.length() > 0) { - String buf = buffer.substring(len); - buffer.setLength(len); - buffer.append("\r\nClazz.newMeth(C$, '$init0$', function () {\r\n"); - buffer.append("var c;if((c = C$.superclazz) && (c = c.$init0$))c.apply(this);\r\n"); - buffer.append(init0Buffer); - buffer.append("}, 1);\r\n"); - buffer.append(buf); - } + buffer.append("}, 1);\n"); - init0Buffer = init0bufold; } + + if (haveFields) + fieldInfo.add$fields$(); + fieldInfo = fieldInfoOld; + haveFields = haveFieldInfoOld; + // add all the methods StringBuffer defaults = new StringBuffer(); @@ -2106,6 +2344,10 @@ && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { if (isLambda) { isStatic = addLambdaClass(node, binding.getFunctionalInterfaceMethod()); } else { + if (isAnnotation) { + buffer.append("C$.prototype.annotationType = function() { return this.getClass$() };\n"); + trailingBuffer.append("C$.$getMembers$ = function() { var a=[];\n"); + } for (Iterator iter = bodyDeclarations.iterator(); iter.hasNext();) { ASTNode element = (ASTNode) iter.next(); if (element instanceof MethodDeclaration) { @@ -2115,7 +2357,10 @@ && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { continue; if (methods != null) { String mname = method.getName(); - if (mname.startsWith("set") || mname.startsWith("get") || mname.startsWith("is")) + if (class_annotationType == NOT_JAXB + || mname.startsWith("set") + || mname.startsWith("get") + || mname.startsWith("is")) methods.add(method); } int defpt = -1; @@ -2135,15 +2380,20 @@ && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { } } } - processMethodDeclaration(method, mnode.parameters(), mnode.getBody(), mnode.isConstructor(), - NOT_LAMBDA); + processMethodDeclaration(mnode, method, mnode.parameters(), mnode.getBody(), + mnode.isConstructor(), NOT_LAMBDA); if (defpt >= 0) { defaults.append(buffer.substring(defpt)); buffer.setLength(defpt); } temp_add$UnqualifiedMethod = addUnqualifiedCurrent; + } else if (element instanceof AnnotationTypeMemberDeclaration) { + processAnnotationTypeMemberDeclaration((AnnotationTypeMemberDeclaration) element); } } + if (isAnnotation) { + trailingBuffer.append("return a}"); + } } if (isInterface) { @@ -2166,12 +2416,13 @@ && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { // add any recently defined static field definitions, assert strings // and Enum constants - if (class_annotationType != ANNOTATION_TYPE_UNKNOWN && methods != null) { + if (class_annotations != null) { // lambda expressions may have an enclosing annotation type, but they will not have methods - ClassAnnotation.addClassAnnotations(class_annotationType, class_annotations, enums, fields, methods, + ClassAnnotation.addClassAnnotations(this, class_annotationType, class_annotations, enums, fields, methods, innerClasses, trailingBuffer); class_annotations = null; class_annotationType = ANNOTATION_TYPE_UNKNOWN; + //class_hasTypeAnnotations = false; } buffer.append(trailingBuffer); // also writes the assert string @@ -2185,11 +2436,11 @@ && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { if (!isInterface) addDefaultConstructor(); if (isEnum) { - buffer.append("var $vals=[];\r\n"); + buffer.append("var $vals=[];\n"); // implicit Enum methods added as trailer - buffer.append("Clazz.newMeth(C$, 'values$', function() { return $vals }, 1);\r\n"); + buffer.append("Clazz.newMeth(C$, 'values$', function() { return $vals }, 1);\n"); buffer.append( - "Clazz.newMeth(C$, 'valueOf$S', function(name) { for (var val in $vals){ if ($vals[val].name == name) return $vals[val]} return null }, 1);\r\n"); + "Clazz.newMeth(C$, 'valueOf$S', function(name) { for (var val in $vals){ if ($vals[val].name == name) return $vals[val]} return null }, 1);\n"); } } @@ -2201,6 +2452,7 @@ && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { buffer.append(")"); this$0Name = this$0Name0; setClassAndBinding(oldShortClassName, oldBinding); + class_annotations = oldAnnotations; } } return isStatic; @@ -2223,9 +2475,49 @@ && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { // } // } - private String getPackageName(ITypeBinding javaClass) { - IPackageBinding p = javaClass.getPackage(); - return (p == null ? "_" : p.getName()); + private void processAnnotationTypeMemberDeclaration(AnnotationTypeMemberDeclaration node) { + + Expression def = node.getDefault(); + SimpleName name = node.getName(); + IMethodBinding mbinding = node.resolveBinding(); + ITypeBinding ret = mbinding.getReturnType(); +// buffer.append("\nClazz.newMeth(C$,'").append(name).append("',function(){});\n"); + int pt = buffer.length(); +// retName = j2sClassObject(ret, retName); +// if (ret.isPrimitive()) { +// retName = NameMapper.getPrimitiveTYPE(retName) + ".TYPE"; +// } else { +// retName += ".class"; +// } + buffer.append("a.push(['" + name + "'," + j2sClassObject(ret) + ","); + if (def == null) { + if (ret.isPrimitive()) { + switch (ret.getName()) { + case "char": + buffer.append("'\0'"); + break; + case "boolean": + buffer.append("false"); + break; + default: + buffer.append("0"); + break; + } + } else { + //if (mbinding.getAnnotations() == null) { + buffer.append("null"); + //} else { + // buffer.append("\"" + mbinding.getAnnotations() + "\""); + //} + } + } else if (ret.isAnnotation()){ + buffer.append("'@" + getFinalJ2SClassName(getJavaClassNameQualified(def.resolveTypeBinding()), FINAL_RAW) + "'"); + } else { + def.accept(this); + } + buffer.append("]);\n"); + trailingBuffer.append(buffer.substring(pt)); + buffer.setLength(pt); } /** @@ -2266,7 +2558,7 @@ private void addDefaultConstructor() { if (class_haveDefaultConstructor) { class_haveDefaultConstructor = false; } else { - buffer.append("\r\nClazz.newMeth(C$);\r\n"); + buffer.append("\nClazz.newMeth(C$);\n"); } } @@ -2278,7 +2570,7 @@ private void addDefaultConstructor() { private void addEnumConstants(EnumDeclaration e, List enums) { List constants = e.enumConstants(); - buffer.append("$vals=Clazz.array(C$,[0]);\r\n"); + buffer.append("$vals=Clazz.array(C$,[0]);\n"); for (int i = 0; i < constants.size(); i++) { EnumConstantDeclaration enumConst = (EnumConstantDeclaration) constants.get(i); enums.add(enumConst); @@ -2292,17 +2584,17 @@ private void addEnumConstants(EnumDeclaration e, List e addClassOrInterface(anonDeclare, dbinding, anonDeclare.bodyDeclarations(), 'a'); // anonDeclare.accept(this); anonName = getJavaClassNameQualified(dbinding); - buffer.append("\r\n"); + buffer.append("\n"); } buffer.append("Clazz.newEnumConst($vals, ") - .append(getFinalMethodNameWith$Params("C$.c$", null, binding, null, false, METHOD_NOTSPECIAL)) + .append(getFinalMethodNameWith$Params("C$.c$", null, binding, null, false, null, METHOD_NOTSPECIAL)) .append(", \""); enumConst.getName().accept(this); buffer.append("\", " + i); addMethodParameterList(enumConst.arguments(), binding, ", [", "]", METHOD_CONSTRUCTOR); if (anonName != null) buffer.append(", ").append(anonName); - buffer.append(");\r\n"); + buffer.append(");\n"); } } @@ -2313,106 +2605,45 @@ private void addEnumConstants(EnumDeclaration e, List e * * @param field the field being declared * @param fields - * @param mode FIELD_DECL_STATIC_NONDEFAULT static fields into $clinit$ - * (values) FIELD_DECL_STATIC_DEFAULTS static fields into buffer - * directly (defaults) FIELD_DECL_NONSTATIC_ALL static variables - * into $init$ (values) and $init0$ (defaults) + * @param isStatic * @return true if anything was written to the buffer */ - private boolean addFieldDeclaration(FieldDeclaration field, int mode) { + @SuppressWarnings("null") + private boolean addFieldDeclaration(FieldDeclaration field, boolean isStatic) { List fragments = field.fragments(); VariableDeclarationFragment identifier = (VariableDeclarationFragment) fragments.get(0); IVariableBinding var = identifier.resolveBinding(); Type nodeType = (var != null && var.getType().isArray() ? null : field.getType()); boolean isPrimitive = (nodeType != null && nodeType.isPrimitiveType()); - @SuppressWarnings("null") - Code code = (isPrimitive ? ((PrimitiveType) nodeType).getPrimitiveTypeCode() : null); // have to check here for final Object = "foo", as that must not be ignored. - boolean isStatic = (mode == FIELD_DECL_STATIC_NONDEFAULT || mode == FIELD_DECL_STATIC_DEFAULTS); boolean checkFinalConstant = ((isPrimitive || var != null && var.getType().getQualifiedName().equals("java.lang.String")) && isStatic && Modifier.isFinal(field.getModifiers())); - boolean needDefault = (mode == FIELD_DECL_NONSTATIC_ALL || - mode == FIELD_DECL_STATIC_DEFAULTS); + boolean haveAnnotations = (!isStatic && var != null && var.getAnnotations().length > 0); - if (needDefault) { - addJ2SDoc(field); - } + addJ2SDoc(field); int len0 = buffer.length(); + int tpt = (isPrimitive ? fieldInfo.getPrimitiveDefaultType(((PrimitiveType) nodeType).getPrimitiveTypeCode()) : -1); for (Iterator iter = fragments.iterator(); iter.hasNext();) { VariableDeclarationFragment fragment = (VariableDeclarationFragment) iter.next(); Expression initializer = fragment.getInitializer(); - if (checkFinalConstant ? getConstantValue(initializer, false) - : isStatic && initializer == null && !needDefault) - continue; - int len = buffer.length(); - - //bufferDebug("fielddecl " + class_localType); - - String prefix = (isStatic ? "C$." : "this.") + getFinalFieldName(fragment.resolveBinding()); - - buffer.append(prefix); - buffer.append("="); // no space here; will check last char for lambda - int len1 = buffer.length(); - - if (initializer == null || needDefault) { - // Route default for this to the $init0$ buffer if nonstatic, or - // straight to the class if static - // if static and not initialized - - // but it cannot be bye or short, because those will use $b$ or $s$, - // which are not defined until the end. -// if (isStatic && isPrimitive && ( -// initializer instanceof NumberLiteral -// && code != PrimitiveType.SHORT && code != PrimitiveType.BYTE -// || initializer instanceof BooleanLiteral -// || initializer instanceof CharacterLiteral) -// ) { -// // let primitives be their default value. This allows, for example, -// // setting a static value to something read just by loading the class. -// addExpressionAsTargetType(initializer, field.getType(), "v", null); -// } else { - buffer.append(code == null ? "null" : getPrimitiveDefault(code)); -// } - buffer.append(";\r\n"); - // - // $clinit$ -- just runs Clazz.load(cl,1) for getting dependencies - // $static$ -- statics; once only; processed later by Clazz.load(cl,2) - // $init0$ -- from within Clazz.newInstance, before any constructors - // $init$ -- from the constructor, just after any super() call or whenever there is no this() call - - // com.falstad.Diffraction.CrossAperature initialization was - // failing. Sequence was: - - // Aperature: calls setDefaults() (new double[][] - // lineXLocations) - // BlockAperature sets lineXLocations = null - // CrossAperature needs the defaults set and fails - - // but needed to be: - - // Aperature: calls setDefaults() (new double[][] - // lineXLocations) - // BlockAperature defines but does not set - // lineXLocations - // CrossAperature sees the created lineXLocations - // created in Aperature - - if (isStatic) - continue; - init0Buffer.append(buffer.substring(len)); - if (initializer == null) { - buffer.setLength(len); + IVariableBinding fbinding = fragment.resolveBinding(); + String name = getFinalFieldName(fbinding); + if (initializer != null) { + if (checkFinalConstant && getConstantValue(initializer, false)) continue; - } - buffer.setLength(len1); + String prefix = (isStatic ? "C$." : "this.") + name; + buffer.append(prefix); + buffer.append("="); // no space here; will check last char for lambda + addExpressionAsTargetType(initializer, field.getType(), "v", null); + buffer.append(";\n"); } - // not static and has an initializer - addExpressionAsTargetType(initializer, field.getType(), "v", null); - buffer.append(";\r\n"); + fieldInfo.addField(isStatic, name, var, tpt); + haveFields = true; } - return (buffer.length() > len0); + boolean wasAdded = (buffer.length() > len0); + return wasAdded && (isStatic || haveAnnotations); } /** @@ -2435,7 +2666,7 @@ private void addInnerTypeInstance(ASTNode node, IMethodBinding constructorMethodDeclaration, String superAnonName, String anonName) { - openNew(superAnonOrInnerClass, (superAnonName == null ? javaInnerClassName : superAnonName), anonName, + openNew(superAnonOrInnerClass == null ? binding : superAnonOrInnerClass, (superAnonName == null ? javaInnerClassName : superAnonName), anonName, constructorMethodDeclaration, -1); // add constructor application arguments: [object, parameters] @@ -2444,8 +2675,6 @@ private void addInnerTypeInstance(ASTNode node, if (outerClassExpr == null) { - ////bufferDebug("this - methodbinding " + class_localType); - buffer.append("this"); } else { outerClassExpr.accept(this); @@ -2519,14 +2748,14 @@ private void addMethodParameterList(List arguments, IMethodBinding methodDecl private void addSuperConstructor(SuperConstructorInvocation node, IMethodBinding methodDeclaration) { if (node == null) { // default constructor - buffer.append("Clazz.super_(C$, this);\r\n"); + buffer.append("Clazz.super_(C$, this);\n"); return; } buffer.append(getFinalMethodNameWith$Params(";C$.superclazz.c$", null, node.resolveConstructorBinding(), null, - false, METHOD_NOTSPECIAL)); - buffer.append(".apply(this"); - addMethodParameterList(node.arguments(), methodDeclaration, ", [", "]", METHOD_CONSTRUCTOR); - buffer.append(")"); + false, null, METHOD_NOTSPECIAL)); + buffer.append(".apply(this,["); + addMethodParameterList(node.arguments(), methodDeclaration, null, null, METHOD_CONSTRUCTOR); + buffer.append("])"); addCallInit(); } @@ -2677,7 +2906,7 @@ TrailingBuffer append(String s) { } String getAssertString() { - return (hasAssert ? "C$.$_ASSERT_ENABLED_ = ClassLoader.getClassAssertionStatus$(C$);\r\n" : ""); + return (hasAssert ? "C$.$_ASSERT_ENABLED_ = ClassLoader.getClassAssertionStatus$(C$);\n" : ""); } public String toString() { @@ -2707,7 +2936,11 @@ void addType(String name) { case 'l': // $l$ // [3][4][pt++] break; } - added += ";\r\n"; + added += ";\n"; + } + + public void insert(int pt, String s) { + buf.insert(pt, s); } } @@ -3111,9 +3344,6 @@ public boolean visit(FieldAccess node) { } else { varBinding = null; } - - //bufferDebug("field - " + expression.getClass().getName()); - expression.accept(this); if (varBinding != null) { @@ -3462,8 +3692,6 @@ public boolean visit(QualifiedName node) { public boolean visit(SimpleName node) { // var x = ... // this.pages .... - //bufferDebug("simplename "); - if (!getConstantValue(node, true)) buffer.append(getFinalNameSimpleQualified(node)); return false; @@ -3528,12 +3756,6 @@ public boolean visit(SimpleType node) { return false; } - private static Map htStrLitCache = new Hashtable<>(); - - static void clearStringLiteralCache() { - htStrLitCache = new Hashtable<>(); - } - public boolean visit(StringLiteral node) { String s = node.getEscapedValue(); if (s.indexOf('\\') < 0) { @@ -3566,8 +3788,6 @@ private String replaceOctal(String s) { * */ public boolean visit(SuperFieldAccess node) { - //bufferDebug("superfield " + class_localType); - buffer.append("this."); buffer.append(getFinalFieldName((IVariableBinding) node.getName().resolveBinding())); return false; @@ -3580,7 +3800,6 @@ public boolean visit(SuperFieldAccess node) { * */ public boolean visit(ThisExpression node) { - ////bufferDebug("this - " + class_localType); if (node.getQualifier() == null && class_localType != LAMBDA_EXPRESSION) { // not x -> this.what() buffer.append("this"); @@ -3596,19 +3815,13 @@ private String getThisRefOrSyntheticReference(ASTNode node, ITypeBinding binding ASTNode classNode = (node == null ? null : getAbstractOrAnonymousParentForNode(node)); if (class_isAnonymousOrLocal || classNode != null && classNode.getParent() != null // CompilationUnit && classNode.getParent().getParent() != null) { - //bufferDebug("thisref1"); // not the top level, but "this" refers to this class - if (binding.getBinaryName().equals(class_typeBinding.getBinaryName())) { - //bufferDebug("thisref1a"); - } else { + if (!binding.getBinaryName().equals(class_typeBinding.getBinaryName())) { // not the top level -- add the synthetic reference. // anonymous and local will not have fully qualified names ref = getSyntheticReference(getJavaClassNameQualified(binding)); - //bufferDebug("thisref1b"); } } - //bufferDebug("thisref2 - " + ref + " " + class_localType); - return ref; } @@ -3642,12 +3855,14 @@ public boolean visit(TypeLiteral node) { */ private void addInterfaceMethodListForLiteral(ITypeBinding binding) { buffer.append(",["); + boolean isAnnotation = binding.isAnnotation(); IMethodBinding[] methods = binding.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (i > 0) buffer.append(","); + String name = methods[i].getName(); buffer.append("'") - .append(getFinalMethodNameWith$Params(null, null, methods[i], null, false, METHOD_LITERAL)) + .append(isAnnotation ? name : getFinalMethodNameWith$Params(null, null, methods[i], null, false, null, METHOD_LITERAL)) .append("'"); } buffer.append("]"); @@ -4007,11 +4222,35 @@ private void addMethodArguments(ITypeBinding[] parameterTypes, boolean methodIsV String op = (isIndexOf && i == 0 ? "q" : "p"); if (i == nparam - 1) { // BH: can't just check for an array; it has to be an array with - // the right number of dimensions - if (nparam != argCount || methodIsVarArgs && paramType.isArray() - && arg.resolveTypeBinding().getDimensions() != paramType.getDimensions() - && !(arg instanceof NullLiteral)) { + // the right component type + // NOTE: This is still not quite right. + // It will not pass a true primitive numeric array type -- + // int[]... value will be [1,2,3], not Int32Array(1,2,3) + // Also, for a generic, we could have a problem if there is only one + // argument, and it is an array. + + ITypeBinding atype = null; + if (nparam != argCount // clearly need [ ] + ||methodIsVarArgs // only one argument - might need [ ] + && !(arg instanceof NullLiteral) // unless it's null + && (!(atype = arg.resolveTypeBinding()).isArray() // otherwise if it is not an array + || !atype.getComponentType().isAssignmentCompatible(paramType.getComponentType().getErasure()) + ) + // or it is not compatible + ) { buffer.append("["); +// +// +// buffer Debug(paramType.getComponentType().getName() + " " +// + (atype != null && atype.isArray() ? atype.getComponentType().getName() : null) +// + " " + (atype != null && atype.isArray() ? atype.getComponentType().isAssignmentCompatible(paramType.getComponentType()) : null) +// + " " + (atype != null && atype.isArray() ? paramType.getComponentType().isAssignmentCompatible(atype.getComponentType()) : null) +// + " " + paramType.getComponentType().getErasure().getName() +// + " " + paramType.getComponentType().getName() +// + " " + (atype != null && atype.isArray() ? atype.getComponentType().isAssignmentCompatible(paramType.getComponentType().getErasure()) : null) +// ); + + for (int j = i; j < argCount; j++) { addExpressionAsTargetType((Expression) arguments.get(j), paramType, op, null); if (j != argCount - 1) { @@ -4134,38 +4373,6 @@ private void addOperand(Expression exp, boolean isToString) { if (needRtParen) buffer.append(")"); } - -// /** -// * fix the buffer for number/char issue when generating simple this.foo = -// * -// * -// * @param code -// */ -// private void fixPrimitiveRightSide(Code code) { -// if (code != PrimitiveType.BOOLEAN) { -// boolean isCharConst = (buffer.charAt(buffer.length() - 1) == '\''); -// if (isCharConst != (getPrimitiveDefault(code).charAt(0) == '\'')) { -// if (!isCharConst) { -// // char c = 33; -// buffer.insert(buffer.lastIndexOf(" ") + 1, "String.fromCharCode("); -// buffer.append(")"); -// } else if (code == PrimitiveType.BYTE) { -// // byte b = 'c' -// buffer.insert(buffer.lastIndexOf(" ") + 1, "($b$[0] = "); -// buffer.append(".$c(), $b$[0])"); -// trailingBuffer.addType("b"); -// } else { -// // int b = 'c' -// buffer.append(".$c()"); -// } -// } -// } -// } - - private String getPrimitiveDefault(Code code) { - return (code == PrimitiveType.BOOLEAN ? "false" : code == PrimitiveType.CHAR ? "'\\0'" : "0"); - } - /** * A general method to handle implicit casting. * @@ -4503,8 +4710,11 @@ static String getJavaClassNameQualified(ITypeBinding binding) { // method returns null for that kind of bindings. // - if (binding == null) + if (binding == null) { return null; + } + if (isTypeOrArrayType(binding)) + return binding.toString(); String name = null, bindingKey; if ((binding.isAnonymous() || binding.isLocal()) && (name = binding.getBinaryName()) == null && (bindingKey = binding.getKey()) != null) @@ -4516,6 +4726,7 @@ static String getJavaClassNameQualified(ITypeBinding binding) { if (name == null) { System.out.println(">>name null?? bn=" + binding.getBinaryName() + " qn=" + binding.getQualifiedName() + " n=" + binding.getName() + " k=" +binding.getKey() + " isAnon" + binding.isAnonymous() + " " + binding.isLocal()); + name = ""; // } } @@ -4523,6 +4734,10 @@ static String getJavaClassNameQualified(ITypeBinding binding) { return name; } + private static boolean isTypeOrArrayType(ITypeBinding binding) { + return binding.isTypeVariable() || binding.isArray() && binding.getComponentType().isTypeVariable(); + } + private static String getJavaClassNameSuperNoBrackets(ITypeBinding typeBinding) { ITypeBinding superclass = typeBinding.getSuperclass(); if (superclass == null) @@ -4558,9 +4773,6 @@ static String stripJavaLang(String name) { */ private String getFinalNameSimpleQualified(SimpleName node) { // xxx.yyy.zzz... - - //bufferDebug("getFinalName " + node); - IBinding binding = node.resolveBinding(); ASTNode parent = node.getParent(); if (parent == null) { @@ -4747,13 +4959,11 @@ String getFinalJ2SClassNameQualifier(Name methodQualifier, ITypeBinding declarin * @return array listing classes that need to be loaded in order */ private String getFinalInnerClassList(ITypeBinding javaClass, String javaClassName) { - String packageName = getPackageName(javaClass); + IPackageBinding p = javaClass.getPackage(); + String packageName = (p == null ? NULL_PACKAGE : p.getName()); if (javaClassName == null) { javaClassName = getJavaClassNameQualified(javaClass); - System.out.println(">>jcn was null! " + javaClassName); -// } else if (javaClassName.length() == 0) { -// // local inner class within an anonymous class -// javaClassName = javaClass.getBinaryName(); + //System.out.println(">>jcn was null! " + javaClassName); } if (javaClassName.indexOf("$lambda") >= 0) return "'" + getFinalJ2SClassName(javaClassName, FINAL_RAW) + "'"; @@ -4894,9 +5104,6 @@ private void visitList(List list, String separator) { } } - private static Map>> genericClassMap = new HashMap>>(); - private static Map> genericClassTypes = new HashMap>(); - /** * Check a class, interface, or Enum binding for generics. * @@ -4906,10 +5113,10 @@ private void visitList(List list, String separator) { * @param binding * @return true if this class could have generic replacements */ - private static boolean checkGenericClass(ITypeBinding topBinding, ITypeBinding binding) { + private boolean checkGenericClass(ITypeBinding topBinding, ITypeBinding binding) { // debugListAllOverrides(binding); if (topBinding == binding) - genericClassMap.put(binding.getKey(), null); + syntheticClassMethodNameMap.put(binding.getKey(), null); // check all superclasses from most super to least super String classKey = binding.getKey(); boolean hasGenerics = (binding.isRawType() || binding.getTypeArguments().length > 0); @@ -4922,9 +5129,9 @@ private static boolean checkGenericClass(ITypeBinding topBinding, ITypeBinding b hasGenerics = checkGenericClass(topBinding, interfaces[i]) || hasGenerics; } if (hasGenerics) { - checkMethodsWithGenericParams(topBinding.getKey(), binding); + addSyntheticMethods(topBinding.getKey(), binding); } else { - genericClassMap.put(classKey, null); + syntheticClassMethodNameMap.put(classKey, null); } return hasGenerics; } @@ -4936,7 +5143,7 @@ private static boolean checkGenericClass(ITypeBinding topBinding, ITypeBinding b * @param topClassKey * @param binding */ - private static void checkMethodsWithGenericParams(String topClassKey, ITypeBinding binding) { + private void addSyntheticMethods(String topClassKey, ITypeBinding binding) { Map classTypes = getGenericClassTypes(binding); if (classTypes == null) return; @@ -4946,18 +5153,19 @@ private static void checkMethodsWithGenericParams(String topClassKey, ITypeBindi IMethodBinding m = methods[i]; String methodName = m.getName(); ITypeBinding[] params = m.getParameterTypes(); - if (params.length == 0) - continue; - String key = m.getKey(); - if (key.indexOf(";T") >= 0 || key.indexOf("(T") >= 0) { - String[] list = new String[params.length]; - for (int j = list.length; --j >= 0;) { - String name = params[j].getName(); - list[j] = name + "|" + classTypes.get(name) + ";"; - } - addGenericClassMethod(classKey, methodName, list); - addGenericClassMethod(topClassKey, methodName, list); + boolean haveGeneric = false; + for (int j = params.length; --j >= 0 && !haveGeneric;) + if (isTypeOrArrayType(params[j])) + haveGeneric = true; + if (!haveGeneric) + return; + String[] list = new String[params.length]; + for (int j = list.length; --j >= 0;) { + String name = params[j].getName(); + list[j] = name + "|" + classTypes.get(name) + ";"; } + addSyntheticMethod(classKey, methodName, list); + addSyntheticMethod(topClassKey, methodName, list); } } @@ -4971,20 +5179,13 @@ private static ASTNode getAbstractOrAnonymousParentForNode(ASTNode node) { return parent; } -// private static ITypeBinding resolveAbstractOrAnonymousBinding(ASTNode node) { -// node = getAbstractOrAnonymousParentForNode(node); -// return (node instanceof AbstractTypeDeclaration ? ((AbstractTypeDeclaration) node).resolveBinding() -// : node instanceof AnonymousClassDeclaration ? ((AnonymousClassDeclaration) node).resolveBinding() -// : null); -// } - /** * Create a map of the class type arguments for an implemented generic class * * @param type * @return a map {T:"java.lang.String",K:"java.lang.Object"} */ - private static Map getGenericClassTypes(ITypeBinding type) { + private Map getGenericClassTypes(ITypeBinding type) { String classKey = type.getKey(); Map classTypes = genericClassTypes.get(classKey); if (classTypes != null) @@ -4999,7 +5200,7 @@ private static Map getGenericClassTypes(ITypeBinding type) { return classTypes; } ITypeBinding[] types = (isGeneric ? typeParams : typeArgs); - classTypes = new Hashtable(); + classTypes = new LinkedHashMap(); // We have to parse this by hand, because I cannot seem to get access to // the // typeParameters of a superclass. Java seems to have erased all that. @@ -5045,8 +5246,8 @@ private static Map getGenericClassTypes(ITypeBinding type) { * @param methodName * @return list of generic types for methods with this name */ - private static List getGenericMethodList(ITypeBinding methodClass, String methodName) { - Map> methodList = genericClassMap.get(methodClass.getKey()); + private List getGenericMethodList(ITypeBinding methodClass, String methodName) { + Map> methodList = syntheticClassMethodNameMap.get(methodClass.getKey()); return (methodList == null ? null : methodList.get(methodName)); } @@ -5057,11 +5258,11 @@ private static List getGenericMethodList(ITypeBinding methodClass, Str * @param methodName * @param list */ - private static void addGenericClassMethod(String classKey, String methodName, String[] list) { + private void addSyntheticMethod(String classKey, String methodName, String[] list) { - Map> classMap = genericClassMap.get(classKey); + Map> classMap = syntheticClassMethodNameMap.get(classKey); if (classMap == null) - genericClassMap.put(classKey, classMap = new Hashtable>()); + syntheticClassMethodNameMap.put(classKey, classMap = new Hashtable>()); List methodList = classMap.get(methodName); if (methodList == null) classMap.put(methodName, methodList = new ArrayList()); @@ -5077,41 +5278,53 @@ private static void addGenericClassMethod(String classKey, String methodName, St * @param node * @param mBinding * @param isConstructor - * @param qualification + * @param aliases + * @param mode * @return j2s-qualified name or an array of j2s-qualified names */ - private String getFinalMethodNameOrArrayForDeclaration(IMethodBinding mBinding, boolean isConstructor, - int qualification) { + String getFinalMethodNameOrArrayForDeclaration(IMethodBinding mBinding, boolean isConstructor, + String aliases, int mode) { String nodeName = mBinding.getName(); String methodName = (isConstructor ? "c$" : nodeName); - String qname = getFinalMethodNameWith$Params(methodName, null, mBinding, null, false, METHOD_NOTSPECIAL); + String qname = getFinalMethodNameWith$Params(methodName, null, mBinding, null, false, null, METHOD_NOTSPECIAL); ITypeBinding methodClass = mBinding.getDeclaringClass(); List names = null; - List methodList = getGenericMethodList(methodClass, nodeName); + - if (methodList != null) { + List methodList = getGenericMethodList(methodClass, nodeName); + if (aliases != null) { + String[] types = aliases.split(","); + String pname = getFinalMethodNameWith$Params(methodName, null, mBinding, types, false, + aliases, METHOD_ALIAS); + if (pname != null) { + names = new ArrayList(); + names.add(pname); + } + + } else if (methodList != null) { names = new ArrayList(); for (int i = methodList.size(); --i >= 0;) { + String[] l = methodList.get(i); String pname = getFinalMethodNameWith$Params(methodName, null, mBinding, methodList.get(i), false, - METHOD_NOTSPECIAL); + null, METHOD_NOTSPECIAL); if (pname != null) names.add(pname); - if (qualification != METHOD_FULLY_QUALIFIED) + if ((mode & METHOD_FULLY_QUALIFIED) == 0) names.add(ensureMethod$Name(methodName, mBinding, getJavaClassNameQualified(methodClass))); } } - if ((qualification & METHOD_$_QUALIFIED) != 0 && !methodName.equals(qname) + if ((mode & METHOD_$_QUALIFIED) != 0 && !methodName.equals(qname) && !classHasNoParameterMethod(methodClass, methodName)) { if (names == null) names = new ArrayList(); names.add(methodName + (methodName.indexOf("$") >= 0 ? "" : methodName.equals("c") ? "$$" : "$")); } - if ((qualification & METHOD_UNQUALIFIED) != 0) { + if ((mode & METHOD_UNQUALIFIED) != 0) { if (names == null) names = new ArrayList(); names.add(methodName); } - if (names == null || names.size() == 0) + if (names == null || names.size() == 0 || mode == METHOD_FULLY_QUALIFIED_JUST_ONE) return "'" + qname + "'"; qname = ",'" + qname + "'"; for (int i = names.size(); --i >= 0;) { @@ -5195,20 +5408,26 @@ private boolean isJava(String className) { return className.length() > 5 && "java.javax".contains(className.substring(0, 5)); } - private static String getParamsAsString(int nParams, String[] genericTypes, ITypeBinding[] paramTypes, - boolean toObject) { + private String getParamsAsString(int nParams, String[] genericTypes, ITypeBinding[] paramTypes, boolean toObject) { StringBuffer sbParams = new StringBuffer(); // if this is a method invocation and has generics, then we alias that boolean haveGeneric = false; for (int i = 0; i < nParams; i++) { + // I guess erasure is not useful here + // this is things like Map vs Map + //if (!paramTypes[i].equals(paramTypes[i].getErasure())) { + // buffer Debug(i + " " + paramTypes[i].getName() + " " + paramTypes[i].getErasure().getName()); + //} String type = j2sGetParamCode(paramTypes[i], true, toObject); if (genericTypes != null) { String genericType = genericTypes[i]; if (genericType != null) { - if (genericType.indexOf("|null") < 0) { - if (genericType.indexOf("|" + getJavaClassNameQualified(paramTypes[i]) + ";") < 0) + boolean isAlias = (genericType.indexOf("|") < 0); + if (isAlias ? genericType.length() > 0 && !genericType.equals("*") + : genericType.indexOf("|null") < 0) { + if (!isAlias && genericType.indexOf("|" + getJavaClassNameQualified(paramTypes[i]) + ";") < 0) return null; - type = "T" + genericType.substring(0, genericType.indexOf("|")); // "O";// + type = (isAlias ? "T" + genericType : "T" + genericType.substring(0, genericType.indexOf("|"))); // "O";// haveGeneric = true; // Originally I was substituting in the generic type // T,V,E,etc., but @@ -5237,18 +5456,29 @@ private static String getParamsAsString(int nParams, String[] genericTypes, ITyp return (toObject && !haveGeneric ? null : sbParams.toString()); } - private static String j2sGetParamCode(ITypeBinding binding, boolean addAAA, boolean asGenericObject) { - String prefix = (removeBracketsAndFixNullPackageName(binding.getKey()).indexOf(":T") >= 0 ? "T" : null); + private String j2sGetParamCode(ITypeBinding binding, boolean addAAA, boolean asGenericObject) { + if (binding.isTypeVariable()) { + // TK, TV, TXYZ + return (asGenericObject ? "O" : "T" + binding.getName()); + } String name = removeBrackets(getJavaClassNameQualified(binding)); - if (!asGenericObject && prefix == null && !binding.isPrimitive()) + if (binding.isArray() && binding.getComponentType().isTypeVariable()) { + // TK[] + if (asGenericObject) { + name = "O" + name.substring(name.indexOf("[")); + } else { + name = "T" + name; + } + } else if (!asGenericObject && !binding.isPrimitive()) { name = NameMapper.fixPackageName(name); + } + String arrays = null; int pt = name.indexOf("["); if (pt >= 0) { arrays = name.substring(pt + (name.indexOf("[L") >= 0 ? 1 : 0)); name = name.substring(0, pt); } - // NOTE: If any of these are changed, they must be changed in j2sSwingJS // as well. // NOTE: These are the same as standard Java Spec, with the exception of @@ -5287,11 +5517,7 @@ private static String j2sGetParamCode(ITypeBinding binding, boolean addAAA, bool name = "S"; break; default: - if (prefix == null) - name = NameMapper.checkClassReplacement(name); - else - name = (asGenericObject ? "O" : prefix + name); // "O";// - name = name.replace("java.lang.", "").replace('.', '_'); + name = NameMapper.checkClassReplacement(name).replace("java.lang.", "").replace('.', '_'); break; } if (arrays != null) { @@ -5399,12 +5625,16 @@ private boolean getConstantValue(Expression node, boolean andWrite) { Object constValue = getConstant(node); StringBuffer sb = null; if (constValue instanceof Number) { + if (!andWrite) + return true; sb = new StringBuffer(); String s = constValue.toString(); if (s.startsWith("-") && buffer.charAt(buffer.length() - 1) == '-') sb.append(' '); sb.append(s); } else if (constValue instanceof Character || constValue instanceof Boolean) { + if (!andWrite) + return true; sb = new StringBuffer(); if (constValue instanceof Character) { sb.append('"'); @@ -5414,10 +5644,11 @@ private boolean getConstantValue(Expression node, boolean andWrite) { sb.append(constValue); } } else if (constValue instanceof String) { + if (!andWrite) + return true; sb = new StringBuffer(); addString((String) constValue, sb); } - if (sb == null) return false; if (andWrite) { @@ -5491,7 +5722,8 @@ private void setMapJavaDoc(PackageDeclaration node) { TagElement tagEl = (TagElement) itr.next(); String tagName = tagEl.getTagName(); if (tagName == null || !tagName.startsWith("@j2sNative") && !tagName.startsWith("@j2sIgnore") - && !tagName.startsWith("@j2sDebug")) + && !tagName.startsWith("@j2sDebug") + && !tagName.startsWith("@j2sAlias")) continue; list.add(comment); break; @@ -5516,7 +5748,7 @@ private void setMapJavaDoc(PackageDeclaration node) { ASTNode item = list.get(i); int factor = 1; if (item instanceof Javadoc) { - logErr("!!Note: @j2s doc ignored because nothing follows it: " + doc.getStartPosition() + "\r\n" + doc); + logErr("!!Note: @j2s doc ignored because nothing follows it: " + doc.getStartPosition() + "\n" + doc); } else { if (item == null) { factor = -1; @@ -5561,6 +5793,32 @@ private boolean addJ2SDoc(ASTNode node) { return ret; } + /** + * Check for @j2sAlias ,,,,P,,,, + * + * @param node + * @return alias + */ + private String checkJ2SMethodDoc(MethodDeclaration node) { + List j2sJavadoc; + if (package_mapBlockJavadoc == null || (j2sJavadoc = getJ2sJavadoc(node, DOC_CHECK_ONLY)) == null) + return null; + for (int i = 0; i < j2sJavadoc.size(); i++) { + Javadoc doc = j2sJavadoc.get(i); + List tags = doc.tags(); + TagElement tag = null; + if (tags == null || tags.size() == 0 + || (tag = NativeDoc.getTag(tags, "@j2sAlias")) == null) + continue; + List fragments = tag.fragments(); + return (fragments == null || fragments.size() == 0 ? null + : fragments.get(0).toString().trim()); + } + return null; + } + + + private List getJ2sJavadoc(ASTNode node, int mode) { // package_mapBlockJavadoc will be null for a no-package class like VARNA.java, @@ -5579,9 +5837,6 @@ private List getJ2sJavadoc(ASTNode node, int mode) { return docs; } - private final static int CHECK_J2S_IGNORE_ONLY = 1; - private final static int CHECK_ANNOTATIONS_ONLY = 2; - private final static int CHECK_J2S_IGNORE_AND_ANNOTATIONS = 3; /** * Method with "j2s*" tag. @@ -5619,36 +5874,37 @@ private boolean checkAnnotations(BodyDeclaration node, int mode) { } private boolean addAnnotation(Annotation annotation, ASTNode node, int mode) { - String qName = annotation.getTypeName().getFullyQualifiedName(); - int idx = qName.indexOf("J2S"); + String name = annotation.getTypeName().getFullyQualifiedName(); + int idx = name.indexOf("J2S"); if (idx >= 0) { - if (mode == CHECK_ANNOTATIONS_ONLY) - return true; - String annName = qName.substring(idx); - if (annName.startsWith("J2SIgnore")) { - return false; + return (mode == CHECK_ANNOTATIONS_ONLY || !name.substring(idx).startsWith("J2SIgnore")); + } + if (global_ignoredAnnotations == null || global_ignoredAnnotations.indexOf(";" + name + ";") >= 0) { + return true; + } + if (class_annotations == null) + class_annotations = new ArrayList(); + String qname = getFinalJ2SClassName(annotation.resolveTypeBinding().getQualifiedName(), FINAL_RAW); + class_annotations.add(new ClassAnnotation(qname, annotation, node)); + if (node instanceof TypeDeclaration) { + try { + throw new NullPointerException(); + } catch (Exception e) { + e.printStackTrace(); } - } else if (qName.equals("Override") || qName.equals("Deprecated") || qName.startsWith("Suppress") - || qName.startsWith("ConstructorProperties")) { - // see java\awt\ScrollPane.js @ConstructorProperties({"scrollbarDisplayPolicy"}) - // ignore - } else { - if (class_annotations == null) - class_annotations = new ArrayList(); - class_annotations.add(new ClassAnnotation(qName, annotation, node)); - if ("Test".equals(qName)) { - class_annotationType = TEST_TYPE; - } else if ("XmlAccessorType".equals(qName)) { + //class_hasTypeAnnotations = true; + } + if (name.startsWith("Xml")) { + if ("XmlAccessorType".equals(name)) { String s = annotation.toString(); class_annotationType = (s.contains("FIELD") ? JAXB_TYPE_FIELD : s.contains("PUBLIC") ? JAXB_TYPE_PUBLIC_MEMBER : s.contains("PROPERTY") ? JAXB_TYPE_PROPERTY : JAXB_TYPE_NONE); - } else if (qName.startsWith("XmlEnum")) { + } else if (name.startsWith("XmlEnum")) { class_annotationType = JAXB_TYPE_ENUM; - } else if (class_annotationType == ANNOTATION_TYPE_UNKNOWN && qName.startsWith("Xml")) { - ///System.out.println(">>>unspecified!"); + } else if (class_annotationType == ANNOTATION_TYPE_UNKNOWN && name.startsWith("Xml")) { class_annotationType = JAXB_TYPE_UNSPECIFIED; - } else if ("XmlElements".equals(qName) && annotation.isSingleMemberAnnotation()) { + } else if ("XmlElements".equals(name) && annotation.isSingleMemberAnnotation()) { Expression e = ((SingleMemberAnnotation) annotation).getValue(); if (e instanceof ArrayInitializer) { @SuppressWarnings("unchecked") @@ -5656,34 +5912,21 @@ private boolean addAnnotation(Annotation annotation, ASTNode node, int mode) { for (int i = expressions.size(); --i >= 0;) { Expression exp = expressions.get(i); if (exp instanceof Annotation) { - class_annotations.add(new ClassAnnotation(qName, (Annotation) exp, node)); + qname = getFinalJ2SClassName(((Annotation) exp).resolveTypeBinding().getQualifiedName(), + FINAL_RAW); + class_annotations.add(new ClassAnnotation(qname, (Annotation) exp, node)); } } } - } + } else if (class_annotationType == ANNOTATION_TYPE_UNKNOWN) { + class_annotationType = NOT_JAXB; } return true; } ///////////////////////////// - /** - * includes @j2sDebug blocks; from j2s.compiler.mode=debug in .j2s - * - */ - static boolean global_j2sFlag_isDebugging = false; - - public static void setDebugging(boolean isDebugging) { - global_j2sFlag_isDebugging = isDebugging; - } - - private static List global_lstMethodsDeclared; - - private static Map global_htMethodsCalled; - - private static boolean global_logAllCalls; - public static void setLogging(List lstMethodsDeclared, Map htMethodsCalled, boolean logAllCalls) { global_lstMethodsDeclared = lstMethodsDeclared; @@ -5740,7 +5983,7 @@ private String fixLogName(String name) { * @param className */ private void appendElementKey(String className) { - buffer.append(ELEMENT_KEY + ("=" + className) + "\r\n"); + buffer.append(ELEMENT_KEY + ("=" + className) + "\n"); } /** @@ -5775,13 +6018,13 @@ public List getElementList() { ))); for (int i = 1; i < parts.length; i++) { js = parts[i]; - int pt = js.indexOf("\r\n"); + int pt = js.indexOf("\n"); String name = js.substring(0, pt); elements.add(name); - js = js.substring(pt + 2); + js = js.substring(pt + 1); String head = "(function(){" + (js.indexOf("$I$(") < 0 && js.indexOf("p$") < 0 ? header_noIncludes : header); - elements.add(head + js + "})();\r\n" + trailer); + elements.add(head + js + "})();\n" + trailer); } resetPrivateVars(); return elements; @@ -5791,19 +6034,14 @@ private void addDummyClassForPackageOnlyFile() { appendElementKey("_$"); buffer.append("var C$=Clazz.newClass(\"_$\");"); appendClinit(); - ClassAnnotation.addClassAnnotations(class_annotationType, class_annotations, null, null, null, null, + ClassAnnotation.addClassAnnotations(this, class_annotationType, class_annotations, null, null, null, null, trailingBuffer); buffer.append(trailingBuffer); addDefaultConstructor(); } public boolean visit(AnnotationTypeDeclaration node) { - System.out.println(">>AnnotationTypeDecl " + node); - return false; - } - - public boolean visit(AnnotationTypeMemberDeclaration node) { - System.out.println(">>AnnotationTypeMembDecl " + node); + addClassOrInterface(node, node.resolveBinding(), node.bodyDeclarations(), '@'); return false; } @@ -5883,7 +6121,7 @@ public boolean visit(WildcardType node) { // return NameMapper.getJavaScriptCollisionIdentifier(localName, true); // } - private static String getFinalFieldName(IVariableBinding binding) { + static String getFinalFieldName(IVariableBinding binding) { return getFinalFieldOrLocalVariableName(binding.getDeclaringClass(), binding.getName()); } @@ -5962,19 +6200,19 @@ static boolean isJ2SInheritedFieldName(ITypeBinding binding, String name) { * Determine the qualified parameter suffix for method names, including * constructors. Now returns name$ for all unparameterized methods not * explicitly excluded. - * * @param javaClassNameForInvocation * @param mBinding * @param genericTypes only in the case of method declarations, * where we are trying to match generic * methods * @param addCallingOption$O + * @param aliases TODO * @param isLiteralOrLambda_C * * @return a fully j2s-qualified method name */ private String getFinalMethodNameWith$Params(String j2sName, String javaClassNameForInvocation, - IMethodBinding mBinding, String[] genericTypes, boolean addCallingOption$O, int specialType) { + IMethodBinding mBinding, String[] genericTypes, boolean addCallingOption$O, String aliases, int specialType) { // The problem is that System.out and System.err are PrintStreams, and // we // do not intend to change those. So in the case that we just wrote @@ -5990,8 +6228,9 @@ static boolean isJ2SInheritedFieldName(ITypeBinding binding, String name) { j2sName = methodName; ITypeBinding declaringClass = mBinding.getDeclaringClass(); String javaClassName = getJavaClassNameQualified(declaringClass); - if (NameMapper.isMethodNonqualified(javaClassName, methodName)) + if (NameMapper.isMethodNonqualified(javaClassName, methodName)) { return j2sName; + } // BH: Note in the next statement, that Map.put$K$V is translated to actual // values @@ -6004,8 +6243,9 @@ static boolean isJ2SInheritedFieldName(ITypeBinding binding, String name) { : mBinding.getMethodDeclaration().getParameterTypes()); int nParams = paramTypes.length; - if (genericTypes != null && genericTypes.length != nParams) + if (genericTypes != null && genericTypes.length != nParams) { return null; + } // xxx() adds $ to become xxx$() iff it is NOT // - (private and not static) @@ -6018,14 +6258,16 @@ static boolean isJ2SInheritedFieldName(ITypeBinding binding, String name) { // functional interface methods are qualified only by "$", not their parameters. // This is not ideal. - IMethodBinding fm = (specialType != METHOD_NOTSPECIAL || javaClassName.equals("java.lang.reflect.Proxy") ? null + IMethodBinding fm = ( + specialType != METHOD_NOTSPECIAL || javaClassName.equals("java.lang.reflect.Proxy") ? null : declaringClass.getFunctionalInterfaceMethod()); if (fm != null && methodName.equals(fm.getName())) return ensureMethod$Name(j2sName, mBinding, null); String s = getParamsAsString(nParams, genericTypes, paramTypes, false); - if (addCallingOption$O && s.indexOf("$T") >= 0 && isJava(javaClassName) && !isJava(class_fullName)) { + if (specialType != METHOD_ALIAS && addCallingOption$O && s.indexOf("$T") >= 0 && isJava(javaClassName) && !isJava(class_fullName)) { + // If the method being called is a Java class and the calling class is NOT a // Java class, // then also add the $O version. @@ -6099,9 +6341,6 @@ static boolean isClassKnown(String qualifiedName) { return knownClassHash.contains(qualifiedName); } - private static Map htClassReplacements; - private static List lstPackageReplacements; - public static void setClassReplacements(String keyValues) { // j2s.class.replacements=org.apache.log4j.*:jalview.jslogger.; htClassReplacements = null; @@ -6363,31 +6602,32 @@ static boolean fieldNameCoversMethod(String fieldName) { static class ClassAnnotation { protected ASTNode node; + String qname; protected Annotation annotation; - private String qName; - protected ClassAnnotation(String qName, Annotation annotation, ASTNode node) { - this.qName = qName; + protected ClassAnnotation(String qname, Annotation annotation, ASTNode node) { this.annotation = annotation; + this.qname = qname; this.node = node; } - - @SuppressWarnings({ "unchecked", "null" }) - public static void addClassAnnotations(int accessType, List class_annotations, + + @SuppressWarnings({ "unchecked" }) + public static void addClassAnnotations(Java2ScriptVisitor visitor, int accessType, List class_annotations, List enums, List fields, List methods, List innerClasses, TrailingBuffer trailingBuffer) { boolean isPackage = (fields == null && enums == null); - int pt = 0, ptBuf = 0; + int nn = 0, ptBuf = 0, ptBuf1 = 0; ASTNode lastNode = null; List fragments = null; String propOrder = null; String lastClassName = null; + String signature = null; for (int i = 0; i < class_annotations.size(); i++) { ClassAnnotation a = class_annotations.get(i); String str = a.annotation.toString(); IAnnotationBinding b = a.annotation.resolveAnnotationBinding(); if (b != null && global_j2sFlag_isDebugging) - System.out.println(">>>" + str + " qname=" + a.qName + " type=" + b.getClass().getName()); + System.out.println("annotation " + str); if (a.annotation instanceof NormalAnnotation) { // @XmlElement(name="test",type=Integer.class) // remove commas, add quotes @@ -6420,6 +6660,7 @@ public static void addClassAnnotations(int accessType, List cla } if (a.node == lastNode) { trailingBuffer.append(","); + trailingBuffer.insert(ptBuf1, "','" + a.qname); } else { lastNode = a.node; String varName = null; @@ -6439,18 +6680,31 @@ public static void addClassAnnotations(int accessType, List cla fragments = field.fragments(); VariableDeclarationFragment identifier = (VariableDeclarationFragment) fragments.get(0); IVariableBinding var = identifier.resolveBinding(); - varName = var.getName(); + varName = getFinalFieldName(var); type = var.getType(); } else if (a.node instanceof MethodDeclaration) { + MethodDeclaration method = (MethodDeclaration) a.node; + IMethodBinding mBinding = method.resolveBinding(); + if (methods != null) + if (methods.contains(mBinding)) + methods.remove(mBinding); + if (accessType != NOT_JAXB) + mBinding = getJAXBGetMethod(mBinding, methods, false); + if (mBinding == null) + continue; + varName = "M:" + mBinding.getName(); + signature = visitor.getFinalMethodNameOrArrayForDeclaration(mBinding, mBinding.isConstructor(), null, METHOD_FULLY_QUALIFIED); + type = mBinding.getReturnType(); + } else if (a.node instanceof AnnotationTypeMemberDeclaration) { MethodDeclaration method = (MethodDeclaration) a.node; IMethodBinding var = method.resolveBinding(); if (methods != null) if (methods.contains(var)) - methods.remove(var); - if (accessType != TEST_TYPE) - var = getJAXBGetMethod(var, methods, false); - if (var == null) - continue; + methods.remove(var); +// if (accessType != NOT_JAXB) +// var = getJAXBGetMethod(var, methods, false); +// if (var == null) +// continue; varName = "M:" + var.getName(); type = var.getReturnType(); } else if (a.node instanceof EnumConstantDeclaration) { @@ -6462,30 +6716,41 @@ public static void addClassAnnotations(int accessType, List cla type = var.getType(); } String className = (type == null ? null - : stripJavaLang(NameMapper.fixPackageName(getJavaClassNameQualified(type)))); + : type.isTypeVariable() ? type.toString() // could be "" + : j2sNonPrimitiveName(type, false)); + //stripJavaLang(NameMapper.fixPackageName(getJavaClassNameQualified(type)) + //String typeref = (type == null ? null : j2sClassObject(type)); if (className != null && className.equals(lastClassName)) { className = "."; + //typeref = "'.'"; } else { lastClassName = className; } - trailingBuffer.append(pt++ == 0 ? "C$.__ANN__ = [[[" : "]],\n [["); + trailingBuffer.append(nn++ == 0 ? "C$.$getAnn$ = function(){ return [\n[[" : "]],\n [["); trailingBuffer.append((varName == null ? null : "'" + varName + "'")); ptBuf = trailingBuffer.buf.length(); - trailingBuffer.append(",'" + className + "'],["); + trailingBuffer.append(",'" + className + "'," + //+ typeref + "," + + signature + ",['" + a.qname + + "']],["); + ptBuf1 = trailingBuffer.buf.length() - 5; } + str = str.replace("'", "\\'"); + int pt = str.indexOf("("); + str = (pt >= 0 ? str.substring(pt+1, str.length() - 1) : ""); trailingBuffer.append("'" + str + "'"); if (propOrder == null && str.indexOf("propOrder=") >= 0) propOrder = str; } - if (pt > 0) { + if (nn > 0) { addTrailingFragments(fragments, trailingBuffer, ptBuf); - if (!isPackage && accessType != TEST_TYPE) + if (!isPackage && accessType != NOT_JAXB) addImplicitJAXBFieldsAndMethods(accessType, trailingBuffer, enums, fields, methods, innerClasses, propOrder); - trailingBuffer.append("]]];\n"); + trailingBuffer.append("]]]}\n"); } if (global_j2sFlag_isDebugging) - System.out.println("pt=" + pt + " " + trailingBuffer); + System.out.println("pt=" + nn + " " + trailingBuffer); } private static String annotationNameValue(String name, Object value) { @@ -6696,7 +6961,7 @@ private void addNode(ASTNode node, boolean isPost) { /** * - * Check for j2sIgnore, j2sDebug, j2sNative + * Check for j2sIgnore, j2sDebug, j2sNative, j2sAlias * * @param javadoc * @param isBlock @@ -6722,7 +6987,7 @@ && addJ2SSourceForTag(buffer, getTag(tags, "@j2sDebug"), i == 0, i == n - 1, return didAdd; } - private static TagElement getTag(List tags, String j2sKey) { + static TagElement getTag(List tags, String j2sKey) { Iterator iter = tags.iterator(); while (iter.hasNext()) { TagElement tagEl = (TagElement) iter.next(); @@ -6745,7 +7010,7 @@ private static TagElement getTag(List tags, String j2sKey) { private static boolean addJ2SSourceForTag(StringBuffer buffer, TagElement tag, boolean addPrefix, boolean addPostfix, boolean isIgnore) { if (isIgnore) { - buffer.append("\r\n{}\r\n"); + buffer.append("\n{}\n"); return true; } if (tag == null) @@ -6757,7 +7022,7 @@ private static boolean addJ2SSourceForTag(StringBuffer buffer, TagElement tag, b String text = commentEl.getText().trim(); buf.append(text); if (text.length() != 0) { - buf.append(text.endsWith(";") || text.indexOf("//") >= 0 ? "\r\n" : " "); + buf.append(text.endsWith(";") || text.indexOf("//") >= 0 ? "\n" : " "); // BH note that all line terminators are removed, // as this causes problems after source cleaning, which may result // in code such as: @@ -6790,9 +7055,9 @@ private static boolean addJ2SSourceForTag(StringBuffer buffer, TagElement tag, b // /** @j2sNative true || */() boolean isInline = code.endsWith("|") || code.endsWith("&") || code.endsWith(":") || code.endsWith("!"); - buffer.append(isInline ? "" : addPrefix ? "{\r\n" : "\r\n"); + buffer.append(isInline ? "" : addPrefix ? "{\n" : "\n"); buffer.append(code); - buffer.append(isInline ? "" : addPostfix ? "\r\n}\r\n" : "\r\n"); + buffer.append(isInline ? "" : addPostfix ? "\n}\n" : "\n"); return true; } @@ -6811,7 +7076,28 @@ public static void dumpStack(String msg) { } } -////////////////// JAVA 8 LAMBDA ADDITIONS ///////////////// + public static String j2sClassObject(ITypeBinding type) { + boolean isArray = type.isArray() && !type.isTypeVariable(); + if (isArray) + type = type.getComponentType(); + String name = (type.isPrimitive() ? type.getName() : j2sNonPrimitiveName(type, true)); + return "'" + (isArray ? name + "[]" : name) + "'"; + } + +// private static Map nonPrimitiveJ2STypeNames = new Hashtable<>(); + + static String j2sNonPrimitiveName(ITypeBinding type, boolean typeAsObject) { + if (type.isTypeVariable()) { + String n = type.toString(); + if (n.startsWith("<")) { + return (typeAsObject ? "Object" : n); + } + } + return stripJavaLang(removeBracketsAndFixNullPackageName(getJavaClassNameQualified(type))); + } + + +//////////////////JAVA 8 LAMBDA ADDITIONS ///////////////// /** * @@ -6914,9 +7200,6 @@ private boolean addLambdaMethodReference(MethodReference node, Expression exp) { private void addLambdaReuse(int pt, String anonName) { String tmp = buffer.substring(pt); buffer.setLength(pt); - -// bufferDebug("addLambdaReuse " + anonName); - anonName = getFinalJ2SClassName(anonName, FINAL_P); buffer.append("(" + anonName + "$||(" + anonName + "$=(") .append(tmp).append(")))"); @@ -6931,7 +7214,6 @@ private void addLambdaBody(ASTNode body) { if (body == null) return; // handled elsewhere buffer.append("("); - //bufferDebug("addLambdaBody - " + class_localType); body.accept(this); buffer.append(");}"); } @@ -6959,15 +7241,13 @@ private boolean addLambdaClass(ASTNode lnode, IMethodBinding mBinding) { List params = node.parameters(); int localType = class_localType; class_localType = LAMBDA_EXPRESSION; - ////bufferDebug("procmethoddecl1"); - processMethodDeclaration(mBinding, params, node.getBody(), false, LAMBDA_EXPRESSION); - ////bufferDebug("procmethoddecl2"); + processMethodDeclaration(null, mBinding, params, node.getBody(), false, LAMBDA_EXPRESSION); class_localType = localType; return true; } if (lnode instanceof CreationReference) { buffer.append("/*lambda_C*/"); - processMethodDeclaration(mBinding, null, null, false, LAMBDA_CREATION); + processMethodDeclaration(null, mBinding, null, null, false, LAMBDA_CREATION); CreationReference node = (CreationReference) lnode; Type ctype = node.getType(); ITypeBinding binding = ctype.resolveBinding(); @@ -6978,7 +7258,7 @@ private boolean addLambdaClass(ASTNode lnode, IMethodBinding mBinding) { // MatchSink::new; addConstructor(binding, mBinding, null, mBinding.getParameterTypes().length); } - buffer.append("});\r\n"); + buffer.append("});\n"); return true; } // method of one type or another @@ -7009,9 +7289,9 @@ private boolean addLambdaClass(ASTNode lnode, IMethodBinding mBinding) { log("??? addLambdaMethod " + lnode.getClass().getName()); return false; } - processMethodDeclaration(mBinding, null, null, false, LAMBDA_METHOD); + processMethodDeclaration(null, mBinding, null, null, false, LAMBDA_METHOD); boolean isStatic = addMethodInvocation(identifier, null, mBinding1, exp, mBinding.getParameterTypes().length); - buffer.append("});\r\n"); + buffer.append("});\n"); return isStatic; } @@ -7127,46 +7407,45 @@ private void resetPrivateVars() { * * @param msg */ - private void bufferDebug(String msg) { + void bufferDebug(String msg) { buffer.append("/*" +msg + "*/"); } - - void debugDumpClass(ITypeBinding binding) { - ITypeBinding[] lst = binding.getTypeParameters(); - - // Check for - these are for the generic class defs themselves - for (int i = 0; i < lst.length; i++) - log(binding.getKey() + "typeP " + i + lst[i].getName()); - - // check for for the implemented classes - lst = binding.getTypeArguments(); - for (int i = 0; i < lst.length; i++) - log(binding.getKey() + "typeA " + i + lst[i].getName()); - - IMethodBinding[] methods = binding.getDeclaredMethods(); - for (int i = methods.length; --i >= 0;) { - IMethodBinding m = methods[i]; - log(getFinalMethodNameWith$Params(m.getName(), null, m, null, false, METHOD_NOTSPECIAL)); - ITypeBinding[] params = m.getParameterTypes(); - for (int j = 0; j < params.length; j++) - log("\t" + params[j].getName()); - - } - } - - static void debugListAllOverrides(ITypeBinding binding) { - IMethodBinding[] jmethods = binding.getDeclaredMethods(); - for (int j = jmethods.length; --j >= 0;) { - IMethodBinding m = jmethods[j]; - ITypeBinding b = null; - while ((b = (b == null ? m.getDeclaringClass() : b.getSuperclass())) != null) { - IMethodBinding[] methods = b.getDeclaredMethods(); - for (int i = methods.length; --i >= 0;) - if (m.overrides(methods[i])) - log("!! " + m.getKey() + " overrides " + methods[i].getKey()); - } - } - } - +// void debugDumpClass(ITypeBinding binding) { +// ITypeBinding[] lst = binding.getTypeParameters(); +// +// // Check for - these are for the generic class defs themselves +// for (int i = 0; i < lst.length; i++) +// log(binding.getKey() + "typeP " + i + lst[i].getName()); +// +// // check for for the implemented classes +// lst = binding.getTypeArguments(); +// for (int i = 0; i < lst.length; i++) +// log(binding.getKey() + "typeA " + i + lst[i].getName()); +// +// IMethodBinding[] methods = binding.getDeclaredMethods(); +// for (int i = methods.length; --i >= 0;) { +// IMethodBinding m = methods[i]; +// log(getFinalMethodNameWith$Params(m.getName(), null, m, null, false, METHOD_NOTSPECIAL)); +// ITypeBinding[] params = m.getParameterTypes(); +// for (int j = 0; j < params.length; j++) +// log("\t" + params[j].getName()); +// +// } +// } +// +// static void debugListAllOverrides(ITypeBinding binding) { +// IMethodBinding[] jmethods = binding.getDeclaredMethods(); +// for (int j = jmethods.length; --j >= 0;) { +// IMethodBinding m = jmethods[j]; +// ITypeBinding b = null; +// while ((b = (b == null ? m.getDeclaringClass() : b.getSuperclass())) != null) { +// IMethodBinding[] methods = b.getDeclaredMethods(); +// for (int i = methods.length; --i >= 0;) +// if (m.overrides(methods[i])) +// log("!! " + m.getKey() + " overrides " + methods[i].getKey()); +// } +// } +// } +// } diff --git a/sources/net.sf.j2s.core/unused/Java2ScriptCompiler.java b/sources/net.sf.j2s.core/unused/Java2ScriptCompiler.java new file mode 100644 index 000000000..e372015dc --- /dev/null +++ b/sources/net.sf.j2s.core/unused/Java2ScriptCompiler.java @@ -0,0 +1,722 @@ +package net.sf.j2s.core; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.CompilationUnit; + +/** + * The main (and currently only operational) Java2Script compiler. + * + * @author Bob Hanson + * + */ +class Java2ScriptCompiler { + + static final String VERSION = CorePlugin.VERSION; + + /** + * The name of the J2S options file, aka as the "Dot-j2s" file. + */ + private static final String J2S_OPTIONS_FILE_NAME = ".j2s"; + + private int nResources, nSources, nJS, nHTML; + + // We copy all non .java files from any directory from which we loaded a + // java file into the site directory + private final HashSet copyResources = new HashSet(); + private Map htMethodsCalled; + private List lstMethodsDeclared; + private String ignoredAnnotations; + + private final static String J2S_COMPILER_STATUS = "j2s.compiler.status"; + private final static String J2S_COMPILER_STATUS_ENABLE = "enable"; + private final static String J2S_COMPILER_STATUS_ENABLED = "enabled"; + + private final static String J2S_COMPILER_JAVA_VERSION = "j2s.compiler.java.version"; + private final static String J2S_COMPILER_JAVA_VERSION_DEFAULT = "8"; + + private static final String J2S_SITE_DIRECTORY = "j2s.site.directory"; + private static final String J2S_SITE_DIRECTORY_DEFAULT = "site"; + + /** + * stop processing files if any file throws an exception, probably because it is a + * + */ + private static final String J2S_BREAK_ON_ERROR = "j2s.break.on.error"; + private static final String J2S_BREAK_ON_ERROR_DEFAULT = "false"; + + /** + * office use only + */ + private static final String J2S_TESTING = "j2s.testing"; + private static final String J2S_TESTING_DEFAULT = "false"; + + /** + * log file name for methods declared + */ + private static final String J2S_LOG_METHODS_DECLARED = "j2s.log.methods.declared"; + private static final String J2S_LOG_METHODS_DECLARED_DEFAULT = ""; + + /** + * log file name for methods called + */ + private static final String J2S_LOG_METHODS_CALLED = "j2s.log.methods.called"; + private static final String J2S_LOG_METHODS_CALLED_DEFAULT = ""; + + private static final String J2S_LOG_ALL_CALLS = "j2s.log.all.calls"; + private static final String J2S_LOG_ALL_CALLS_DEFAULT = "false"; + + private static final String J2S_EXCLUDED_PATHS = "j2s.excluded.paths"; + private static final String J2S_EXCLUDED_PATHS_DEFAULT = ""; + + /** + * Added 3.2.6 + */ + private static final String J2S_COMPILER_READ_ANNOTATIONS = "j2s.compiler.read.annotations"; + private static final String J2S_COMPILER_READ_ANNOTATIONS_DEFAULT = "true"; + private static final String J2S_COMPILER_IGNORED_ANNOTATIONS = "j2s.compiler.ignored.annotations"; + private static final String J2S_COMPILER_IGNORED_ANNOTATIONS_DEFAULT = "CallerSensitive;ConstructorProperties;Deprecated;Override;SaveVarargs;SuppressWarnings;"; + + private static final String J2S_COMPILER_NONQUALIFIED_PACKAGES = "j2s.compiler.nonqualified.packages"; + private static final String J2S_COMPILER_NONQUALIFIED_PACKAGES_DEFAULT = ""; + + private static final String J2S_COMPILER_NONQUALIFIED_CLASSES = "j2s.compiler.nonqualified.classes"; + private static final String J2S_COMPILER_NONQUALIFIED_CLASSES_DEFAULT = ""; + + private static final String J2S_COMPILER_MODE = "j2s.compiler.mode"; + private static final String J2S_COMPILER_MODE_DEFAULT = "nodebug"; + private static final String J2S_COMPILER_MODE_DEBUG = "debug"; + + private static final String J2S_TEMPLATE_HTML = "j2s.template.html"; + private static final String J2S_TEMPLATE_HTML_DEFAULT = "template.html"; + + private static final String J2S_CLASS_REPLACEMENTS = "j2s.class.replacements"; + private static final String J2S_CLASS_REPLACEMENTS_DEFAULT = ""; + + private Properties props; + private String htmlTemplate = null; + + private String projectFolder; + + private String projectPath; + + private String j2sPath; + + private String logDeclared; + + private boolean logAllCalls; + + private String logCalled; + + private String excludedPaths; + + private String siteFolder; + + private boolean testing; + + private List lstExcludedPaths; + + private boolean isCleanBuild; + + boolean isCompilationParticipant; + + private ASTParser astParser; + + private IJavaProject project; + + private boolean isDebugging; + + private boolean breakOnError; + + public boolean doBreakOnError() { + return breakOnError; + } + + static boolean isActive(IJavaProject project) { + try { + return new File(project.getProject().getLocation().toOSString(), J2S_OPTIONS_FILE_NAME).exists(); + } catch (@SuppressWarnings("unused") Exception e) { + return false; + } + } + + Java2ScriptCompiler() { + // initialized only once using CompilationParticipant; every time using + // older Builder idea + } + + /** + * only for CompilationParticipant + * @param isClean + */ + void startBuild(boolean isClean) { + // at the beginning of a clean build, clear data + isCleanBuild = isClean; + htmlTemplate = null; + if (isClean) { + copyResources.clear(); + lstMethodsDeclared = null; + htMethodsCalled = null; + } + } + + /** + * from Java2ScriptCompilationParticipant.java + * + * get all necessary .j2s params for a build + * + * @param project + * @param isCompilationParticipant + * @return true if this is a j2s project and is enabled + * + */ + boolean initializeProject(IJavaProject project, boolean isCompilationParticipant) { + + try { + nResources = nSources = nJS = nHTML = 0; + this.project = project; + this.isCompilationParticipant = isCompilationParticipant; + if (!isActive(project)) { + // the file .j2s does not exist in the project directory -- skip this project + return false; + } + projectPath = "/" + project.getProject().getName() + "/"; + projectFolder = project.getProject().getLocation().toOSString(); + props = new Properties(); + try { + File j2sFile = new File(projectFolder, J2S_OPTIONS_FILE_NAME); + props.load(new FileInputStream(j2sFile)); + String status = getProperty(J2S_COMPILER_STATUS, J2S_COMPILER_STATUS_ENABLED); + if (!J2S_COMPILER_STATUS_ENABLE.equalsIgnoreCase(status) + && !J2S_COMPILER_STATUS_ENABLED.equalsIgnoreCase(status)) { + if (getFileContents(j2sFile).trim().length() == 0) { + writeToFile(j2sFile, getDefaultJ2SFile()); + } else { + // not enabled + return false; + } + } + } catch (FileNotFoundException e1) { + e1.printStackTrace(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + int jslLevel = AST.JLS8; + try { + String ver = getProperty(J2S_COMPILER_JAVA_VERSION, J2S_COMPILER_JAVA_VERSION_DEFAULT); + jslLevel = Integer.parseInt(ver); + } catch (Exception e) { + // ignore + } + if (jslLevel > 8) { + System.out.println("#j2s compiler version > 8 is experimental only"); + } + try { + astParser = ASTParser.newParser(jslLevel); + System.out.println("#j2s compiler version set to " + jslLevel); + } catch (Exception e) { + System.out.println("#j2s compiler version " + jslLevel + " could not be set; using 8"); + astParser = ASTParser.newParser(AST.JLS8); + } + + breakOnError = !"false".equalsIgnoreCase(getProperty(J2S_BREAK_ON_ERROR, J2S_BREAK_ON_ERROR_DEFAULT)); + + // includes @j2sDebug blocks + isDebugging = J2S_COMPILER_MODE_DEBUG + .equalsIgnoreCase(getProperty(J2S_COMPILER_MODE, J2S_COMPILER_MODE_DEFAULT)); + + File file; + siteFolder = getProperty(J2S_SITE_DIRECTORY, J2S_SITE_DIRECTORY_DEFAULT); + siteFolder = projectFolder + "/" + siteFolder; + j2sPath = siteFolder + "/swingjs/j2s"; + System.out.println("#j2s writing to " + j2sPath); + // method declarations and invocations are only logged + // when the designated files are deleted prior to building + + logDeclared = (isCompilationParticipant && !isCleanBuild ? null + : getProperty(J2S_LOG_METHODS_DECLARED, J2S_LOG_METHODS_DECLARED_DEFAULT)); + if (logDeclared != null) { + if (!(file = new File(projectFolder, logDeclared)).exists()) { + lstMethodsDeclared = new ArrayList(); + System.err.println("logging methods declared to " + file); + } + logDeclared = projectFolder + "/" + logDeclared; + } + logAllCalls = false; + + logCalled = (isCompilationParticipant && !isCleanBuild ? null + : getProperty(J2S_LOG_METHODS_CALLED, J2S_LOG_METHODS_CALLED_DEFAULT)); + if (logCalled != null) { + if (!(file = new File(projectFolder, logCalled)).exists()) { + htMethodsCalled = new Hashtable(); + System.err.println("logging methods called to " + file); + } + logCalled = projectFolder + "/" + logCalled; + logAllCalls = "true".equalsIgnoreCase(getProperty(J2S_LOG_ALL_CALLS, J2S_LOG_ALL_CALLS_DEFAULT)); + } + + excludedPaths = getProperty(J2S_EXCLUDED_PATHS, J2S_EXCLUDED_PATHS_DEFAULT); + + lstExcludedPaths = null; + + if (excludedPaths != null) { + lstExcludedPaths = new ArrayList(); + String[] paths = excludedPaths.split(";"); + for (int i = 0; i < paths.length; i++) + if (paths[i].trim().length() > 0) + lstExcludedPaths.add(projectPath + paths[i].trim() + "/"); + if (lstExcludedPaths.size() == 0) + lstExcludedPaths = null; + } + + boolean readAnnotations = !"false".equals(getProperty(J2S_COMPILER_READ_ANNOTATIONS, J2S_COMPILER_READ_ANNOTATIONS_DEFAULT)); + + ignoredAnnotations = (readAnnotations ? getProperty(J2S_COMPILER_IGNORED_ANNOTATIONS, J2S_COMPILER_IGNORED_ANNOTATIONS_DEFAULT) : null); + + testing = "true".equalsIgnoreCase(getProperty(J2S_TESTING, J2S_TESTING_DEFAULT)); + + String prop = getProperty(J2S_COMPILER_NONQUALIFIED_PACKAGES, J2S_COMPILER_NONQUALIFIED_PACKAGES_DEFAULT); + // older version of the name + String nonqualifiedPackages = getProperty(J2S_COMPILER_NONQUALIFIED_CLASSES, + J2S_COMPILER_NONQUALIFIED_CLASSES_DEFAULT); + nonqualifiedPackages = (prop == null ? "" : prop) + + (nonqualifiedPackages == null ? "" : (prop == null ? "" : ";") + nonqualifiedPackages); + if (nonqualifiedPackages.length() == 0) + nonqualifiedPackages = null; + + String classReplacements = getProperty(J2S_CLASS_REPLACEMENTS, J2S_CLASS_REPLACEMENTS_DEFAULT); + + String htmlTemplateFile = getProperty(J2S_TEMPLATE_HTML, J2S_TEMPLATE_HTML_DEFAULT); + if (htmlTemplate == null) { + file = new File(projectFolder, htmlTemplateFile); + if (!file.exists()) { + String html = getDefaultHTMLTemplate(); + System.out.println("J2S creating new htmltemplate file " + file); + writeToFile(file, html); + } + htmlTemplate = getFileContents(file); + System.out.println("J2S using HTML template " + file); + } + + Java2ScriptVisitor.setAnnotating(ignoredAnnotations); + Java2ScriptVisitor.setDebugging(isDebugging); + Java2ScriptVisitor.setLogging(lstMethodsDeclared, htMethodsCalled, logAllCalls); + + Java2ScriptVisitor.NameMapper.setNonQualifiedNamePackages(nonqualifiedPackages); + Java2ScriptVisitor.NameMapper.setClassReplacements(classReplacements); + + if (isCleanBuild) + Java2ScriptVisitor.startCleanBuild(); + + } catch (Exception e) { + System.out.println("error " + e + " " + e.getStackTrace()); + e.printStackTrace(); + return false; + } + + return true; + } + + boolean excludeFile(IFile javaSource) { + String filePath = javaSource.getFullPath().toString(); + if (lstExcludedPaths != null) { + for (int i = lstExcludedPaths.size(); --i >= 0;) + if (filePath.startsWith(lstExcludedPaths.get(i))) { + return true; + } + } + return false; + } + /** + * from Java2ScriptCompilationParticipant.java + * + * process the source file into JavaScript using the JDT abstract syntax + * tree parser and visitor + * + * @param javaSource + */ + boolean compileToJavaScript(IFile javaSource) { + nSources++; + String sourceLocation = javaSource.getLocation().toString(); + org.eclipse.jdt.core.ICompilationUnit createdUnit = JavaCore.createCompilationUnitFrom(javaSource); + astParser.setSource(createdUnit); + // note: next call must come before each createAST call + astParser.setResolveBindings(true); + CompilationUnit root = (CompilationUnit) astParser.createAST(null); + // If the Java2ScriptVisitor is ever extended, it is important to set the project. + // Java2ScriptVisitor#addClassOrInterface uses getClass().newInstance().setproject(project). + Java2ScriptVisitor visitor = new Java2ScriptVisitor().setProject(project, testing); + try { + + // transpile the code + + root.accept(visitor); + + // generate the .js file(s) in the site directory + + outputJavaScript(visitor, j2sPath); + + logMethods(logCalled, logDeclared, logAllCalls); + + // add the HTML files in the site directory + + addHTML(visitor.getAppList(true), siteFolder, htmlTemplate, true); + addHTML(visitor.getAppList(false), siteFolder, htmlTemplate, false); + } catch (Throwable e) { + e.printStackTrace(); + e.printStackTrace(System.out); + // find the file and delete it. + String outPath = j2sPath; + String rootName = root.getJavaElement().getElementName(); + rootName = rootName.substring(0, rootName.lastIndexOf('.')); + String packageName = visitor.getMyPackageName(); + if (packageName != null) { + File folder = new File(outPath, packageName.replace('.', File.separatorChar)); + outPath = folder.getAbsolutePath(); + File jsFile = new File(outPath, rootName + ".js"); //$NON-NLS-1$ + if (jsFile.exists()) { + System.out.println("J2S deleting " + jsFile); + jsFile.delete(); + } + } + return false; + } + String packageName = visitor.getMyPackageName(); + if (packageName != null) { + int pt = packageName.indexOf("."); + if (pt >= 0) + packageName = packageName.substring(0, pt); + if (!copyResources.contains(packageName)) { + copyResources.add(packageName); + pt = sourceLocation.lastIndexOf("/" + packageName + "/"); + if (pt <= 0) { + // also don't allow "" root directory + if (!"_".equals(packageName)) + System.out.println("J2S ignoring bad sourceLocation for package \"" + packageName + "\": " + sourceLocation); + } else { + String sourceDir = sourceLocation.substring(0, pt); + File src = new File(sourceDir, packageName); + File dest = new File(j2sPath, packageName); + copySiteResources(src, dest); + } + } + } + return true; + } + + //// private methods //// + + + private void logMethods(String logCalled, String logDeclared, boolean doAppend) { + if (htMethodsCalled != null) + try { + File file = new File(logCalled); + file.createNewFile(); + FileOutputStream fos = new FileOutputStream(file, doAppend); + for (String key : htMethodsCalled.keySet()) { + String val = htMethodsCalled.get(key); + fos.write(key.getBytes()); + if (!val.equals("-")) { + fos.write(','); + fos.write(val.getBytes()); + } + fos.write('\n'); + } + fos.close(); + } catch (Exception e) { + System.err.println("Cannot log to " + logCalled + " " + e.getMessage()); + } + if (lstMethodsDeclared != null) + try { + File file = new File(logDeclared); + file.createNewFile(); + FileOutputStream fos = new FileOutputStream(file, true); + for (int i = 0, n = lstMethodsDeclared.size(); i < n; i++) { + fos.write(lstMethodsDeclared.get(i).getBytes()); + fos.write('\n'); + } + fos.close(); + } catch (Exception e) { + System.err.println("Cannot log to " + logDeclared + " " + e.getMessage()); + } + } + + private String getProperty(String key, String def) { + String val = props.getProperty(key); + if (val == null) + val = def; + System.out.println(key + " = " + val); + if (val != null && val.indexOf("<") == 0) + val = null; + return val; + } + + private void outputJavaScript(Java2ScriptVisitor visitor, String j2sPath) { + + // fragments[0] is package] + List elements = visitor.getElementList(); + + // BH all compression is deprecated --- use Google Closure Compiler + + String packageName = visitor.getMyPackageName(); + for (int i = 0; i < elements.size();) { + String elementName = elements.get(i++); + String element = elements.get(i++); + createJSFile(j2sPath, packageName, elementName, element); + } + } + + private void createJSFile(String j2sPath, String packageName, String elementName, String js) { + if (packageName != null) { + File folder = new File(j2sPath, packageName.replace('.', File.separatorChar)); + j2sPath = folder.getAbsolutePath(); + if (!folder.exists() || !folder.isDirectory()) { + if (!folder.mkdirs()) { + throw new RuntimeException("J2S failed to create folder " + j2sPath); //$NON-NLS-1$ + } + } + } + File f = new File(j2sPath, elementName + ".js"); + if (isDebugging) + System.out.println("J2S Compiler creating " + f); + nJS++; + writeToFile(f, js); + } + + private String getFileContents(File file) { + try { + StringBuilder sb = new StringBuilder(); + FileInputStream is = new FileInputStream(file); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } catch (@SuppressWarnings("unused") IOException e) { + // + } + return null; + } + + private void writeToFile(File file, String data) { + if (data == null) + return; + try { + FileOutputStream os = new FileOutputStream(file); + os.write(data.getBytes("UTF-8")); + os.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * The default .j2s file. Replaces .j2s only if it is found but is empty. + * + * OK, I know this should be a resource. + * + * + * + */ + + private String getDefaultJ2SFile() { + + return "#j2s default configuration file created by net.sf.java2script_" + + CorePlugin.VERSION + " " + new Date() + "\n\n" + + "#enable the Java2Script transpiler -- comment out to disable\n" + + "j2s.compiler.status=enable\n" + + "\n" + + "\n" + + "# destination directory for all JavaScript\n" + + "j2s.site.directory=site\n" + + "\n" + + "# uncomment j2s.* lines to process:\n" + + "\n" + + "# a semicolon-separated list of package-level file paths to be excluded\n" + + "#j2s.excluded.paths=test;testng\n" + + "\n" + + "# output file name for logging methods declared - delete the file to regenerate a listing \n" + + "#j2s.log.methods.declared=methodsDeclared.csv\n" + + "\n" + + "#output file name for logging methods called - delete the file to regenerate a listing\n" + + "#j2s.log.methods.called=methodsCalled.csv\n" + + "\n" + + "#if set, every instance of methods called will be logged\n" + + "#otherwise, only the first call to a method will be logged \n" + + "#output will be comma-separated: called method,caller class \n" + + "#j2s.log.all.calls=true\n" + + "\n" + + "# a semicolon-separated list of packages that contain classes for which the method names\n" + + "# in their classes should not be \"qualified\" to indicate their parameter types. \n" + + "# This option is useful if you have an API interface in Java that refers to JavaScript \n" + + "# methods such as calling window or jQuery functions or the methods in Clazz or J2S. \n" + + "# The classes must not have any methods that are overloaded - with the\n" + + "# same name but different paramater type, as JavaScript will only see the last one.\n" + + "#j2s.compiler.nonqualified.packages=org.jmol.api.js;jspecview.api.js\n" + + "\n" + + "# uncomment to add debugging output. Start eclipse with the -consoleLog option to see output.\n" + + "#j2s.compiler.mode=debug\n" + + "\n" + + "# a semicolon-separated list of from->to listings of package (foo.) or class (foo.bar) \n" + + "# replacements to be made. This option allows for having one class or package used in Java\n" + + "# and another used in JavaScript. Take care with this. All methods in both packages must\n" + + "# have exactly the same parameter signature. We use it in Jalview to provide a minimal\n" + + "# JavaScript implementation of a large third-party library while still using that library's\n" + + "# jar file in Java.\n" + + "#j2s.class.replacements=org.apache.log4j.->jalview.javascript.log4j.\n" + + "\n" + + "# uncomment and change if you do not want to use the template.html file created for you\n" + + "# in your project directory. A default template file will be created by the transpiler \n" + + "# directory if there is none there already.\n" + + "#j2s.template.html=template.html\n"; + } + /** + * The default template file. The user can specify another in the .j2s file + * using template.html=..... + * + * @return default template with _NAME_, _CODE_, and _MAIN_ to fill in. + */ + private String getDefaultHTMLTemplate() { + return "\n" + + "\n" + + "\n" + + "SwingJS test _NAME_\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "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\n" + + "
\n" + + "\n" + + "\n"; + } + + /** + * Create a test HTML file for the applet or application. It will go into + * /site. + * + * @param appList + * @param siteFolder + * @param template + * @param isApplet + */ + private void addHTML(ArrayList appList, String siteFolder, String template, boolean isApplet) { + if (appList == null || template == null) + return; + for (int i = appList.size(); --i >= 0;) { + String cl = appList.get(i); + String _NAME_ = cl.substring(cl.lastIndexOf(".") + 1); + String fname = cl.replaceAll("\\.", "_") + (isApplet ? "_applet" : "") + ".html"; + cl = "\"" + cl + "\""; + String _MAIN_ = (isApplet ? "null" : cl); + String _CODE_ = (isApplet ? cl : "null"); + template = template.replace("_NAME_", _NAME_).replace("_CODE_", _CODE_).replace("_MAIN_", _MAIN_); + System.out.println("J2S creating " + siteFolder + "/" + fname); + writeToFile(new File(siteFolder, fname), template); + nHTML++; + } + } + + private FileFilter filter = new FileFilter() { + + @Override + public boolean accept(File pathname) { + return pathname.isDirectory() || !pathname.getName().endsWith(".java"); + } + + }; + + private void copySiteResources(File from, File dest) { + System.out.println("j2s copying resources from " + from + " to " + dest); + copyNonclassFiles(from, dest); + } + + + private void copyNonclassFiles(File dir, File target) { + if (dir.equals(target)) + return; + File[] files = dir.listFiles(filter); + File f = null; + if (files != null) + try { + if (!target.exists()) + Files.createDirectories(target.toPath()); + for (int i = 0; i < files.length; i++) { + f = files[i]; + if (f == null) { + // + } else if (f.isDirectory()) { + copyNonclassFiles(f, new File(target, f.getName())); + } else { + String path = f.toPath().toString(); + if (!copyResources.contains(path)) { + // + copyResources.add(path); + nResources++; + Files.copy(f.toPath(), new File(target, f.getName()).toPath(), + StandardCopyOption.REPLACE_EXISTING); + if (isDebugging) + System.out.println("J2S copied to site: " + path); + } + } + } + } catch (IOException e1) { + System.out.println("J2S error copying " + f + " to " + target); + e1.printStackTrace(); + } + } + + public void finalizeProject() { + System.out.println("J2S processed " + + nSources + " .java file" + Java2ScriptCompilationParticipant.plural(nSources) + + ", created " + nJS + " .js file" + Java2ScriptCompilationParticipant.plural(nJS) + + " and " + nHTML + " .html file" + Java2ScriptCompilationParticipant.plural(nHTML) + + ", copied " + nResources + " resource" + Java2ScriptCompilationParticipant.plural(nResources)); + } + +} diff --git a/sources/net.sf.j2s.core/unused/Java2ScriptVisitor.java b/sources/net.sf.j2s.core/unused/Java2ScriptVisitor.java new file mode 100644 index 000000000..9a0dd0327 --- /dev/null +++ b/sources/net.sf.j2s.core/unused/Java2ScriptVisitor.java @@ -0,0 +1,7353 @@ +/******************************************************************************* + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * +://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Zhou Renjian - initial API and implementation + *******************************************************************************/ +package net.sf.j2s.core; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.ArrayAccess; +import org.eclipse.jdt.core.dom.ArrayCreation; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.ArrayType; +import org.eclipse.jdt.core.dom.AssertStatement; +import org.eclipse.jdt.core.dom.Assignment; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.BlockComment; +import org.eclipse.jdt.core.dom.BodyDeclaration; +import org.eclipse.jdt.core.dom.BooleanLiteral; +import org.eclipse.jdt.core.dom.BreakStatement; +import org.eclipse.jdt.core.dom.CastExpression; +import org.eclipse.jdt.core.dom.CatchClause; +import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.Comment; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.ConditionalExpression; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.ContinueStatement; +import org.eclipse.jdt.core.dom.CreationReference; +import org.eclipse.jdt.core.dom.DoStatement; +import org.eclipse.jdt.core.dom.EmptyStatement; +import org.eclipse.jdt.core.dom.EnhancedForStatement; +import org.eclipse.jdt.core.dom.EnumConstantDeclaration; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.ExpressionMethodReference; +import org.eclipse.jdt.core.dom.ExpressionStatement; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ForStatement; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.IMemberValuePairBinding; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.IPackageBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.IfStatement; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.InfixExpression; +import org.eclipse.jdt.core.dom.Initializer; +import org.eclipse.jdt.core.dom.InstanceofExpression; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.LabeledStatement; +import org.eclipse.jdt.core.dom.LambdaExpression; +import org.eclipse.jdt.core.dom.LineComment; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberRef; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.MethodRef; +import org.eclipse.jdt.core.dom.MethodRefParameter; +import org.eclipse.jdt.core.dom.MethodReference; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.NullLiteral; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.ParameterizedType; +import org.eclipse.jdt.core.dom.ParenthesizedExpression; +import org.eclipse.jdt.core.dom.PostfixExpression; +import org.eclipse.jdt.core.dom.PrefixExpression; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.core.dom.PrimitiveType.Code; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.QualifiedType; +import org.eclipse.jdt.core.dom.ReturnStatement; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.Statement; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SuperFieldAccess; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.SuperMethodReference; +import org.eclipse.jdt.core.dom.SwitchCase; +import org.eclipse.jdt.core.dom.SwitchStatement; +import org.eclipse.jdt.core.dom.SynchronizedStatement; +import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.core.dom.TextElement; +import org.eclipse.jdt.core.dom.ThisExpression; +import org.eclipse.jdt.core.dom.ThrowStatement; +import org.eclipse.jdt.core.dom.TryStatement; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclarationStatement; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jdt.core.dom.TypeMethodReference; +import org.eclipse.jdt.core.dom.TypeParameter; +import org.eclipse.jdt.core.dom.UnionType; +import org.eclipse.jdt.core.dom.VariableDeclarationExpression; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.eclipse.jdt.core.dom.WhileStatement; +import org.eclipse.jdt.core.dom.WildcardType; + +// TODO There is a limitation in implementations relating to Spliterators. Specifically, +// Spliterators.EmptySpliterator.Of[Int|Long|Double] needed to have +// tryAdvance(XxxConsumer consumer) included manually. +// +// In Java, the interface method Spliterator.OfInt.tryAdvance(IntConsumer action) +// is covered sufficiently by Spliterators.EmptySpliterator.tryAdvance(Consumer action), +// but that has the wrong name to cover for the IntConsumer variation in SwingJS. +// +// The issue is that one interface, Spilterator.OfInt, refers to the IntConsumer variant: +// +//default boolean tryAdvance(Consumer action) { +// if (action instanceof IntConsumer) { +// return tryAdvance((IntConsumer) action); +// } +// +// but that is not explicitly included in EmptySpliterator.OfInt. Instead, +// that call must be made to tryAdvance(Consumer), which is found in Spliterator.EmptySpliterator. +// +// The simple solution was to add tryAdvance(XxxConsumer) to Spliterators.EmptySpliterator.OfXxx. +// But, of course, this is still a hack. +// +// todo: j2sdoc in static field showing up in default static block only, not in initializer block. + +// BH 2019.12.19 3.2.6-v0 C$.$clinit$=2 adds C$.$fields$, Clazz._getFields + +// BH 2019.11.20 3.2.5-v1 fix and refactoring for FINAL $finals$ fix throughout java.util.stream + +// NOTE: All of the original (complicated and only partially working) nested block-counting code in +// relation to final variable use and declaration has been removed. It is replaced by a simple method +// that tracks use of this.$final$ within a class block using a HashSet and then uses that +// HashSet as the basis for the {a:a,b:this.$finals$.b} mapping listFinalVariables. +// This fixed all of the stream issues. See Test_Local, Test_java8, Test_Class. + +// BH 2019.12.15 3.2.5-v4 fix for not getting name for boxing +// BH 2019.12.15 3.2.5-v4 fix for local class within anonymous class not getting name +// BH 2019.12.12 3.2.5-v3 fix for enums == null in annotations +// BH 2019.12.07 3.2.5-v3 fix for lambda expression with $$ must not be cached +// BH 2019.12.06 3.2.5-v2 fix for try(resources) not closing those +// BH 2019.11.18 3.2.5-v0 fix for anonymous subclass of a local class not handling finals +// BH 2019.11.18 3.2.5-v0 fix for main method that throws exception not generating html test +// BH 2019.11.18 3.2.5-v0 fix for lambda expressions in classes with annotations +// BH 2019.11.12 3.2.5-v0 fix for string literals with \n \nn \nnn octals, but "use strict" does not allow for this. +// BH 2019.11.12 3.2.5-v0 fix for static object being created before initialization is complete. +// BH 2019.11.12 3.2.5-v0 proper semantic versioning + +// NOTE: The changes in 3.2.5-v0 are backward-compatible with .js files made from earlier versions. +// But files created with 3.2.5-v0 cannot be run using older SwingJS runtimes. +// NOTE: Initializing version 3.2.5-v0 requires starting Eclipse with the -clean option if +// the plugin as in Eclipse has the "3.2.4" version still affixed. + +// Case of org.biojava.bio.symbol.Location interface creating a static reference +// to [RangeLocation extends AbstractRangeLocation] during the instantiation +// of [PointLocation extends AbstractRangeLocation], causing RangeLocation to +// not pull in AbstractRangeLocation's interface methods, since that was all +// happening before AbstractRangeLocation's initialization was complete. +// +//Location static RangeLocation rloc = new RangeLocation() +// AbstractLocation +// AbstractRangeLocation +// PointLocation +// RangeLocation +// +// Basically, a RangeLocation object was created that did not have +// AbstractRangeLocation's prototype methods. +// +// The primary design issue here was that we did not recognize that class +// dependency (superclass and interface) loading must fully precede execution +// of , where static fields are fully initialized. Obviously Java does this, +// as in Java all dependencies must be present before compiling can occur. +// The solution was to carry out the class loading first, and only when necessary +// (class access via a reference to a static field or method, or during +// superclass/interface instantiation. +// +// BH 2019.10.18 fix for "P$.licationShutdownHooks" missing first three letters "app" +// BH 2019.09.07 adds optimization for lambda methods that do not have finals +// BH 2019.08.29 fix for boxing of binary representation 0b01... (Google Closure Compiler bug) +// BH 2019.05.13 fix for Math.getExponent, ulp, nextDown, nextUp, nextAfter needing qualification +// BH 2019.05.13 fix for Function reference in new Foo()::test(...) +// BH 2019.04.03 fix for @j2sIgnore not including {} +// note to self: It is an annoyance that Eclipse does not recognize an annotation edit +// as a need to recompile automatically +// TODO: Create a j2s configuration tag "j2s.jaxb.packages= package;package;package..." +// that will direct the transpiler to create __ANN__ in any class within this package. +/** + * + * @author zhou renjian 2006-12-3 + * @author Bob Hanson 2017-2020 + * + * + * + */ +public class Java2ScriptVisitor extends ASTVisitor { + + // 3.2.4: + // C$.$clinit$ -- runs Clazz.load(cl,1) for getting dependencies + // C$ static defaults included in class loading code + // C$.$init0$ -- object defaults from within Clazz.newInstance, before any constructors + // C$.$init$ -- object declarations from the constructor, just after any super() call or whenever there is no this() call + // C$.__ANN__ [[[...]]] annotation references, without types + // no @interface + // no field value typing + // no member return value typing + + // 3.2.5: + // C$.$clinit$ = 1 -- tells Clazz to use C$.$statics$ + // C$ static defaults included in class loading code + // C$.$static$ -- static declarations; once only; processed later by Clazz.load(cl,2) + // C$.$init0$ -- object defaults from within Clazz.newInstance, before any constructors + // C$.$init$ -- object declarations from the constructor, just after any super() call or whenever there is no this() call + // C$.__ANN__ [[[...]]] annotation references without types + // no @interface + // no field value typing + // no member return value typing + + // 3.2.6: + // C$.$clinit$ = 2 tells Clazz to use $fields$ + // C$.$fields$ = [[static][object]];referenced by Clazz.getFields() + // C$.$init$ -- object declarations from the constructor, just after any super() call or whenever there is no this() call + // C$.$static$ and C$.$init0$ created dynamically + // C$.$getMembers$ -- for @interface + // C$.$getAnn$ -- return annotation references, with types + // java.lang.Class can use C$.$fields$ to get field types for reflection + + static final String VERSION = CorePlugin.VERSION; + + private static final int NOT_LAMBDA = 0; + private static final int LAMBDA_METHOD = 1; + private static final int LAMBDA_CREATION = 3; + private static final int LAMBDA_EXPRESSION = 5; + + private final static int METHOD_NOTSPECIAL = 0; + private final static int METHOD_LITERAL = 1; + private final static int METHOD_LAMBDA_C = 3; + + private final static int METHOD_CONSTRUCTOR = 4; + private final static int METHOD_INDEXOF = 8; + private final static int METHOD_ISQUALIFIED = 16; + private final static int METHOD_NULLEXPRESSION = 32; + + private static final int NOT_LOCAL = 0; + private static final int REALLY_LOCAL_CLASS = 1; + private static final int ANON_CLASS = 2; + private static final int LAMBDA_UNWRAPPED = 4; + private static final int LAMBDA_WRAPPED = 8; + + + private final static String CHARCODEAT0 = ".$c()"; + + private static final String NULL_PACKAGE = "_"; + + private final static int CHECK_J2S_IGNORE_ONLY = 1; + private final static int CHECK_ANNOTATIONS_ONLY = 2; + private final static int CHECK_J2S_IGNORE_AND_ANNOTATIONS = 3; + + static final int ANNOTATION_TYPE_UNKNOWN = 0; + final static int JAXB_TYPE_NONE = 1; + final static int JAXB_TYPE_FIELD = 2; + final static int JAXB_TYPE_PUBLIC_MEMBER = 3; + final static int JAXB_TYPE_PROPERTY = 4; + static final int JAXB_TYPE_ENUM = 5; + static final int JAXB_TYPE_UNSPECIFIED = 6; + // UNSPECIFIED indicates that some @Xml... annotation was used, but + // no XMLAccesorType was indicated for the class, so + // this must be determined at run time based on the package. In this case we + // introduce the !XMLPublic(true|false) annotation into C$.__ANN__ so that at + // least we know whether this field is public or not. This is then a flag to the + // marshaller to filter fields based on whether we are using PUBLIC_MEMBER or + // not from the package or superclass. Could work. Not tested. + + // The main problem here is that we don't necessarily know if this is JAXB or not + + static final int NOT_JAXB = 0x10; + + static Map htClassReplacements; + static List lstPackageReplacements; + + + private static List global_lstMethodsDeclared; + private static Map global_htMethodsCalled; + private static boolean global_logAllCalls; + + private static Map>> genericClassMap = new HashMap>>(); + private static Map> genericClassTypes = new HashMap>(); + + private static Map htStrLitCache = new Hashtable<>(); + + /** + * includes @j2sDebug blocks; from j2s.compiler.mode=debug in .j2s + * + */ + static boolean global_j2sFlag_isDebugging = false; + + /** + * list of annotations to ignore or null to ignore ALL + * + */ + private static String global_ignoredAnnotations = ";" + + "CallerSensitive;" + + "ConstructorProperties;" + + "Deprecated;" + + "Override;" + + "SaveVarargs;" + + "SuppressWarnings;" + ; + + + public static void setDebugging(boolean isDebugging) { + global_j2sFlag_isDebugging = isDebugging; + } + + + public static void setAnnotating(String ignoredAnnotations) { + global_ignoredAnnotations = (ignoredAnnotations == null ? null : ";" + ignoredAnnotations + ";"); + } + + static void startCleanBuild() { + htStrLitCache = new Hashtable<>(); + } + + + /** + * annotations collected for a class + */ + private List class_annotations; + private int class_annotationType = ANNOTATION_TYPE_UNKNOWN; +// private boolean class_hasTypeAnnotations; + + + + private IJavaProject global_project; + + /** + * multipurpose flag for testing development ideas. + * + */ + private boolean global_testing; + // make private fields properties of p1$ ? + // the problem only shows up with a2s subclasses of Swing components + // because they could declare the same private variable and not get + // caught by the compiler, since they do not subclass that class. + // + // possible issues: + // 1) We may have changed some awt, sun, and javax fields from private to public + // 2) Is there an issue with inner classes referencing outer-class private + // fields? + + private String package_name; + + /** + * track the names for I$$[...] + */ + private StringBuffer package_includes = new StringBuffer(); + + /** + * map class names to I$$[] index + * + */ + private Map package_htIncludeNames = new Hashtable<>(); + + /** + * I$$[] index counter + * + */ + private int[] package_includeCount = new int[1]; + + /** + * "final or effectively final" variable stashes + * + */ + private Map package_htFinalVarToJ2sName = new Hashtable<>(); + private Map> package_htClassKeyToVisitedFinalVars = new Hashtable<>(); + private Set class_visitedFinalVars = new HashSet(); + + /** + * a flag to indicate that the expression being evaluated is an ArrayAccess type + * and so must be integerized with |0 + */ + private boolean temp_processingArrayIndex; + + /** + * functionalInterface methods add the name$ qualifier even if they are + * parameterized + * + */ + private boolean temp_add$UnqualifiedMethod; + + // the three key elements of any class + + private String class_fullName = ""; // test.Test_ + private String class_shortName = ""; // Test_ + private ITypeBinding class_typeBinding; + private boolean class_isAnonymousOrLocal; + /** + * default constructor found by visit(MethodDeclaration) + */ + private boolean class_haveDefaultConstructor; + + /** + * Set the three key elements for the current class. Called only by + * addClassOrInterface. + * + * @param className + * @param binding + */ + private void setClassAndBinding(String className, ITypeBinding binding) { + class_typeBinding = binding; + class_shortName = className; + class_fullName = package_name + '.' + class_shortName; + } + + private Java2ScriptVisitor setInnerGlobals(Java2ScriptVisitor parent, ASTNode node) { + package_name = parent.package_name; + package_htIncludeNames = parent.package_htIncludeNames; + package_includeCount = parent.package_includeCount; + package_includes = parent.package_includes; + package_mapBlockJavadoc = parent.package_mapBlockJavadoc; + + // final and effectively final references + + package_htFinalVarToJ2sName = parent.package_htFinalVarToJ2sName; + package_htClassKeyToVisitedFinalVars = parent.package_htClassKeyToVisitedFinalVars; + + // flag for wrapping lambda Class::method syntax with $$ function + + class_localType = parent.class_localType; + + this$0Name = parent.class_fullName; + innerNode = node; + + return this; + } + + private ASTNode innerNode; + private String this$0Name; + + private static IType appletType; + + public Java2ScriptVisitor() { + // default constructor is necessary for addClassOrInterface + + // TODO how to compare a type with a subclass of JApplet? +// try { +// appletType = project.findType("javax.swing.JApplet"); +// } catch (JavaModelException e) { +// logErr("Java2ScriptVisitor could not find javax.swing.JApplet"); +// } + } + + public Java2ScriptVisitor setProject(IJavaProject project, boolean testing) { + this.global_testing = testing; + this.global_project = project; + return this; + } + + public String getMyPackageName() { + return package_name; + } + + /** + * Buffer that keeps all compiled *.js. + */ + StringBuffer buffer = new StringBuffer(); + + private char getLastCharInBuffer() { + return (buffer.length() == 0 ? '\0' : buffer.charAt(buffer.length() - 1)); + } + + private final static int FIELD_INFO_OBJECT = 0; + private final static int FIELD_INFO_STATIC = 1; + private final static int FIELD_BOOLEAN = 0; + private final static int FIELD_BYTE = 1; + private final static int FIELD_CHAR = 2; + private final static int FIELD_DOUBLE = 3; + private final static int FIELD_FLOAT = 4; + private final static int FIELD_INT = 5; + private final static int FIELD_LONG = 6; + private final static int FIELD_SHORT = 7; + private final static int FIELD_STRING = 8; + private final static int FIELD_OTHER = 9; + private final static int FIELD_COUNT = 10; + + // order above can be changed, but typeCode order must then be adapted + + private final static String typeCodes = "ZBCDFILHSO"; + + private class FieldInfo { + @SuppressWarnings("unchecked") + private List[][] fields = new ArrayList[2][FIELD_COUNT]; + + private String lastType = null; + + FieldInfo() { + } + + void add$fields$() { + StringBuffer buf = buffer; + buf.append("\nC$.$fields$=["); + for (int i = 0; i < 2; i++) { + List[] fieldData = fields[i]; + int lastType = -1; + for (lastType = FIELD_COUNT; --lastType >= 0;) + if (fieldData[lastType] != null) + break; + // Skip statics if there are none + if (i == 1 && lastType < 0) + break; + buf.append(i == 0 ? "[" : "\n,["); + String sep = "'"; + for (int j = 0; j <= lastType; j++) { + List list = fieldData[j]; + if (list == null) + continue; + buf.append(sep).append(typeCodes.charAt(j)).append("',["); + for (int k = 0; k < list.size(); k++) { + buf.append(k == 0 ? "'" : ",'").append(list.get(k)).append("'"); + } + sep = ",'"; + buf.append("]"); + } + buf.append("]"); + } + buf.append("]\n"); + +// if (init0Buffer.length() > 0) { +// String buf = buffer.substring(len); +// buffer.setLength(len); +// buffer.append("\nClazz.newMeth(C$, '$init0$', function () {\n"); +// buffer.append("var c;if((c = C$.superclazz) && (c = c.$init0$))c.apply(this);\n"); +// buffer.append(init0Buffer); +// buffer.append("}, 1);\n"); +// buffer.append(buf); +// } + } + + @SuppressWarnings("null") + void addField(boolean isStatic, String name, IVariableBinding var, int tpt) { + int fpt = (isStatic ? FIELD_INFO_STATIC : FIELD_INFO_OBJECT); + List[] fieldData = fields[fpt]; + String typeName = null; + if (tpt < 0) { + typeName = j2sNonPrimitiveName(var.getType(), false); + tpt = (typeName.equals("String") ? FIELD_STRING : FIELD_OTHER); + } + List lst = fieldData[tpt]; + if (lst == null) + lst = fieldData[tpt] = new ArrayList(); + if (tpt == FIELD_OTHER) { + if (typeName.equals(lastType)) { + lst.add("+" + name); + } else { + lst.add(name); + lst.add(lastType = typeName); + } + } else { + lst.add(name); + } + } + + int getPrimitiveDefaultType(Code code) { + switch (code.toString()) { + case "boolean": + return FIELD_BOOLEAN; + case "byte": + return FIELD_BYTE; + case "char": + return FIELD_CHAR; + case "double": + return FIELD_DOUBLE; + case "float": + return FIELD_FLOAT; + case "int": + return FIELD_INT; + case "long": + return FIELD_LONG; + case "short": + return FIELD_SHORT; + default: + return -1; + } + } + + + } + + private FieldInfo fieldInfo; + private boolean haveFields; + + private ArrayList applets, apps; + + private void addApplication() { + if (apps == null) + apps = new ArrayList(); + apps.add(class_fullName); + } + + private boolean checkAddApplet(ITypeBinding binding) { + if (Modifier.isAbstract(binding.getModifiers())) + return false; + IType bound = (IType) binding.getJavaElement(); + // How to compare this with JApplet? + ITypeBinding b = binding; + while ((binding = binding.getSuperclass()) != null) { + String name = binding.getQualifiedName(); + if ("javax.swing.JApplet".equals(name) || "java.applet.Applet".equals(name)) { + if (applets == null) + applets = new ArrayList(); + applets.add(class_fullName); + return true; + } + if (name.startsWith("java.") || name.startsWith("javax")) + return false; + } + return false; + } + + public ArrayList getAppList(boolean isApplets) { + return (isApplets ? applets : apps); + } + + private boolean isUserApplet; + + private int class_localType = NOT_LOCAL; + + public boolean visit(CompilationUnit node) { + resetPrivateVars(); + return true; + } + + public boolean visit(PackageDeclaration node) { + setMapJavaDoc(node); + List annotations = node.annotations(); + if (annotations != null && annotations.size() > 0) { + for (int i = 0; i < annotations.size(); i++) + addAnnotation((Annotation) annotations.get(i), node, CHECK_ANNOTATIONS_ONLY); + if (class_annotationType == ANNOTATION_TYPE_UNKNOWN) + class_annotationType = JAXB_TYPE_PUBLIC_MEMBER; + } + setPackage(node.getName().toString()); + return false; + } + +// public boolean visit(AnonymousClassDeclaration node) { + // anonymous will never come through here. It will be routed directly to + // addClassOrInterface +// addClassOrInterface(node, node.resolveBinding(), node.bodyDeclarations(), 'a'); +// return false; +// } + + /** + * Set the package name and start the package header + * + * @param packageName package name or null to check to see if this is a + * top-level window. applet + * + */ + private void setPackage(String packageName) { + if (packageName == null) { + if (package_name != null) + return; + packageName = NULL_PACKAGE; + } + package_name = packageName; + package_includes = new StringBuffer(); + buffer.append("var P$="); + if (NameMapper.isJ2sClazzPackage(package_name)) { + buffer.append(packageName); + } else { + buffer.append("Clazz.newPackage(\"").append(packageName).append("\")"); + } + buffer.append(",I$=[];\n"); + } + + public boolean visit(AssertStatement node) { + buffer.append("Clazz.assert(C$, this, function(){return "); + addExpressionAsTargetType(node.getExpression(), Boolean.TYPE, "r", null); + Expression msg = node.getMessage(); + if (msg != null) { + buffer.append("}, function(){return "); + msg.accept(this); + } + buffer.append("});\n"); + trailingBuffer.hasAssert = true; + return false; + } + + /** + * Only specially process blocks if they are method declarations. Never process + * these for constructors. + */ + + public boolean visit(Block node) { + buffer.append("{\n"); + ASTNode parent = node.getParent(); + if (parent instanceof MethodDeclaration && !((MethodDeclaration) parent).isConstructor() + || parent instanceof Initializer) { + Javadoc javadoc = ((BodyDeclaration) parent).getJavadoc(); + if (javadoc != null) { + List list = new ArrayList(); + list.add(javadoc); + return !NativeDoc.addJ2sJavadocs(buffer, list, false); + } + } + return true; + } + + public void endVisit(Block node) { + // look for trailing j2sNative block just before the end of a block + getJ2sJavadoc(node, DOC_ADD_POST); + buffer.append("}"); + } + + public boolean visit(BreakStatement node) { + buffer.append("break"); + addLabel(node.getLabel(), false); + return false; + } + + /** + * top-level new Foo() inner static new Foo.Bar() + * + * lambda expression Runnable r2 = () -> System. out.println("Hello world + * two!"); + * + * new MouseListener() {...} + * + */ + public boolean visit(ClassInstanceCreation node) { + AnonymousClassDeclaration anonDeclare = node.getAnonymousClassDeclaration(); + ITypeBinding binding = node.resolveTypeBinding(); + if (binding == null) + return false; + if (binding.isLocal()) { + // method-local classes or anonymous classes + // includes anonymous, despite JLS spec that anonymous classes are not local + processLocalInstance(node, anonDeclare, binding, binding, getJavaClassNameQualified(binding), NOT_LAMBDA, + anonDeclare == null ? REALLY_LOCAL_CLASS : ANON_CLASS); + return false; + } + IMethodBinding constructorMethodBinding = node.resolveConstructorBinding(); + if (binding.isTopLevel() || isStatic(binding)) { + // standard new Foo() or new ClassX.Foo() + addConstructor(binding, + // node.getType(), + constructorMethodBinding, node.arguments(), -1); + } else { + // inner nonstatic class + addInnerTypeInstance(node, + binding, + binding, + getJavaClassNameQualified(binding), + node.getExpression(), + (constructorMethodBinding == null ? null : constructorMethodBinding.getMethodDeclaration()), + null, + null); + } + return false; + } + + private void addConstructor(ITypeBinding javaClass, + // Type type, + IMethodBinding constructorMethodBinding, List arguments, int lambdaArity) { + String javaClassName = getJavaClassNameQualified(javaClass); + if ("java.lang.Object".equals(javaClassName)) { + buffer.append(" Clazz.new_()"); + return; + } + String finalQualifiedClassName = getFinalJ2SClassName(javaClassName, FINAL_RAW); + String prefix = null, postfix = null; + boolean isDefault = false; + if ("String".equals(finalQualifiedClassName)) { + // special treatment for String -- see j2sSwingJS.js + buffer.append(" String.instantialize("); + } else if (NameMapper.isOneOf(finalQualifiedClassName, NameMapper.noConstructorNames)) { + // look out for java.lang.Integer and the like -- just pass it + // directly + // Replace new Boolean with Boolean.from because new + // Boolean("false") returns true in JavaScript. + // JavaScript considers any string to be true while java only + // considers the string "true" to be true + if (finalQualifiedClassName.equals("Boolean")) + buffer.append(" Boolean.from("); + else + buffer.append(" new ").append(finalQualifiedClassName).append("("); + } else { + openNew(javaClass, javaClassName, null, constructorMethodBinding, lambdaArity); + isDefault = (arguments != null && arguments.isEmpty()); + prefix = ",["; + postfix = "]"; + } + if (lambdaArity >= 0) { + buffer.append(",["); + String params = getLambdaParamList(constructorMethodBinding, lambdaArity); + String finals = listFinalVariables(package_htClassKeyToVisitedFinalVars.get(javaClass.getKey()), false); + if (finals != null) { + buffer.append("this,").append(finals); + if (params.length() > 0) + buffer.append(","); + } + buffer.append(params).append("]"); + } else if (!isDefault) { + IMethodBinding constructorMethodDeclaration = (constructorMethodBinding == null ? null + : constructorMethodBinding.getMethodDeclaration()); + addMethodParameterList(arguments, constructorMethodDeclaration, prefix, postfix, METHOD_CONSTRUCTOR); + } + buffer.append(")"); + } + + /** + * Start a new Clazz.new_() call for class creation or inner classes. Uses + * Clazz.load for dynamic loading + * + * @param javaClassName the class name to use if there is no + * anonymous class name + * @param anonJavaName the name of the anonymous class or lambda + * class that has just been defined + * @param constructorMethodBinding the specified constructor for this + * instantiation + * @param lambdaArity parameterTypes[].length for a lambda method + * or -1 for non-lambda classes + */ + private void openNew( + ITypeBinding javaClass, + String javaClassName, + String anonJavaName, + IMethodBinding constructorMethodBinding, int lambdaArity) { + + + buffer.append("Clazz.new_("); + if (javaClass.isParameterizedType()) { + Iterator map = getGenericClassTypes(javaClass).keySet().iterator(); + ITypeBinding[] args = javaClass.getTypeArguments(); + buffer.append("1,{"); + String sep = ""; + for (int i = 0; i < args.length; i++) { + buffer.append(sep).append(map.next()).append(":\"").append(j2sNonPrimitiveName(args[i], true)).append("\""); + sep = ","; + } + buffer.append("},"); +// ITypeBinding[] args = javaClass.getTypeArguments(); +// for (int i = 0; i < args.length; i++) { +// bufferDebug("new pt " + javaClass.getKey() + " " + args[i].getName()); +// } + } + + + String finalQualifiedName; + if (anonJavaName == null) { + // not inner + finalQualifiedName = getFinalJ2SClassName(javaClassName, FINAL_PC); + if (!finalQualifiedName.equals("C$")) + finalQualifiedName = getFinalJ2SClassNameQualifier(null, javaClass, javaClassName, FINAL_ESCAPECACHE | FINAL_NEW); + } else { + // use the lambda name directly without caching or changing. + finalQualifiedName = getFinalJ2SClassName(anonJavaName, FINAL_P); + } + if (constructorMethodBinding == null) { + // an interface instance, so no constructor + buffer.append(finalQualifiedName + ".$init$"); + return; + } + String qName = getFinalMethodNameWith$Params(finalQualifiedName + ".c$", null, constructorMethodBinding, null, + false, lambdaArity >= 0 ? METHOD_LAMBDA_C : METHOD_NOTSPECIAL); + // if no parameters, we just give the name of the class, not the constructor + buffer.append(qName.endsWith(".c$") ? finalQualifiedName : qName); + } + + /** + * Construct the class name from a Type. + * + * @param type + * @return + */ + private String getClassJavaNameForType(Type type) { + if (type instanceof QualifiedType) { + QualifiedType qualType = (QualifiedType) type; + return getClassJavaNameForType(qualType.getQualifier()) + "." + qualType.getName().getIdentifier(); + } + if (type instanceof ArrayType) + return getClassJavaNameForType(((ArrayType) type).getElementType()); + if (type instanceof ParameterizedType) + return getClassJavaNameForType(((ParameterizedType) type).getType()); + if (type instanceof SimpleType) { + ITypeBinding binding = ((SimpleType) type).resolveBinding(); + return getJavaClassNameQualified(binding); + } + + // PrimitiveType + // WildcardType + // other types? + return null; + } + +// private String localName; // temporary only + + /** + * Runnable r = new Runnable(){public void run(){System. out.println("OK");}}; + * + * or + * + * Runnable r = () -> System. out.println("OK"); + * + * a class definition and an instantiation at the same time. " + * + * @param node + * @param anonymousClassDeclaration + * @param binding + * @param javaInnerClassName includes method-local class names and + * anonymous class names + * @param isLambda + * @param isClassTrulyLocal + * + * @return anonymous name only if there are no finals + */ + private String processLocalInstance(ASTNode node, ASTNode anonymousClassDeclaration, ITypeBinding binding, + ITypeBinding innerSuperClass, String javaInnerClassName, int lambdaType, int localType) { + + // In the case of local classes, the declaration is dissociated from the + // instantiation, so we need to cache the final string "{m:m,b:b,...}" at + // creation time and recover it here. + + // String finals; + boolean isStatic = true; + if (localType != REALLY_LOCAL_CLASS) { + // lambda and anonymous classes are defined inline. + isStatic = addInnerDeclaration(anonymousClassDeclaration == null ? node : anonymousClassDeclaration, binding, + lambdaType, false, null); + } + IMethodBinding constructorDeclaration; + String anonymousSuperclassName, anonName; + if (lambdaType == NOT_LAMBDA) { + // anonymous class + IMethodBinding constructorBinding = ((ClassInstanceCreation) node).resolveConstructorBinding(); + anonymousSuperclassName = getJavaClassNameSuperNoBrackets(binding); + if (anonymousSuperclassName != null) + innerSuperClass = binding.getSuperclass(); + constructorDeclaration = (constructorBinding == null || anonymousSuperclassName == null ? null + : constructorBinding.getMethodDeclaration()); + // force direct .$init$() call rather than .c$() + // only if the anonymous instance is for an interface + anonName = (constructorDeclaration == null ? javaInnerClassName : null); + } else { + // force .$init$() call rather than .c$() for lambda classes + constructorDeclaration = null; + anonymousSuperclassName = null; + anonName = getMyJavaClassNameLambda(false); + } + int oldLocalType = class_localType; + class_localType = localType; + addInnerTypeInstance(node, binding, innerSuperClass, javaInnerClassName, null, constructorDeclaration, + anonymousSuperclassName, anonName); + class_localType = oldLocalType; + if (lambdaType != LAMBDA_METHOD && localType != REALLY_LOCAL_CLASS) + buffer.append(")"); // end of line (..., ...) + if (!isStatic) + return null; + + String key= binding.getKey(); + Set set = package_htClassKeyToVisitedFinalVars.get(key); + return (set == null || set.isEmpty() ? anonName : null); + } + + /** + * Add the class declaration for inner, local, and lambda classes. + * + * @param node + * @param declaration + * @param binding + * @param lambdaType + * @param isTrulyLocal not anonymous and not lambda -- private to a method + * @param bodyDeclarations + */ + private boolean addInnerDeclaration(ASTNode node, ITypeBinding binding, int lambdaType, boolean isTrulyLocal, + List bodyDeclarations) { + + boolean isStatic = true; + boolean wasAnonymous = class_isAnonymousOrLocal; + String key = binding.getKey(); + class_isAnonymousOrLocal = true; + Set lastVisitedVars = class_visitedFinalVars; + Set myVisitedVars = class_visitedFinalVars = new HashSet<>(); + package_htClassKeyToVisitedFinalVars.put(key, myVisitedVars); + if (lambdaType != NOT_LAMBDA) { + isStatic = addClassOrInterface(node, binding, null, 'm'); + if (lambdaType == LAMBDA_METHOD) + buffer.append("); return "); + else + buffer.append(", "); + } else if (isTrulyLocal) { + // todo: do we need to do the check below also for local classes? + addClassOrInterface(node, binding, bodyDeclarations, 'l'); + } else { + buffer.append("("); + ITypeBinding superclass = binding.getSuperclass(); + if (superclass != null) { + // anonymous subclass of local must get visited finals for local superclass. This was a critical find. + Set superfinals = (superclass.isLocal() ? package_htClassKeyToVisitedFinalVars.get(superclass.getKey()) : null); + if (superfinals != null) + myVisitedVars.addAll(superfinals); + } + addClassOrInterface(node, binding, ((AnonymousClassDeclaration) node).bodyDeclarations(), 'a'); + buffer.append(", "); + } + class_visitedFinalVars = lastVisitedVars; + class_isAnonymousOrLocal = wasAnonymous; + return isStatic; + } + + + /** + * Generated final variable list for anonymous class creation. Update the + * lastVisitedVars list + * + * @param visitedVars the list of all variables that have been visited + * as final for this class or any of its inner + * classes + * @param allowFinalsInListing allow this.$final$ for values in the array + * listing that is passed to Clazz.new_; will be + * false for lambda expressions + * @return + */ + private String listFinalVariables(Set visitedVars, boolean allowFinalsInListing) { + if (visitedVars == null || visitedVars.size() == 0) + return null; + StringBuffer buf = new StringBuffer(); + int n = 0; + buf.append("{"); + for (Iterator iter = visitedVars.iterator(); iter.hasNext();) { + IVariableBinding v = iter.next(); + if (n++ > 0) + buf.append(','); + String j2sName = package_htFinalVarToJ2sName.get(v); + boolean isFinal = allowFinalsInListing && (class_typeBinding != v.getDeclaringMethod().getDeclaringClass()); + if (isFinal) + package_htClassKeyToVisitedFinalVars.get(class_typeBinding.getKey()).add(v); + buf.append(j2sName).append(':').append(isFinal ? "this.$finals$." : "").append(j2sName); + } + buf.append("}"); + return buf.toString(); + } + + public boolean visit(ConstructorInvocation node) { + IMethodBinding constructorBinding = node.resolveConstructorBinding(); + List arguments = node.arguments(); + buffer.append(getFinalMethodNameWith$Params("C$.c$", null, constructorBinding, null, false, METHOD_NOTSPECIAL)) + .append(".apply(this"); + IMethodBinding methodDeclaration = (constructorBinding == null ? null + : constructorBinding.getMethodDeclaration()); + addMethodParameterList(arguments, methodDeclaration, ", [", "]", METHOD_CONSTRUCTOR); + buffer.append(");\n"); + return false; + } + + public boolean visit(ContinueStatement node) { + buffer.append("continue"); + addLabel(node.getLabel(), false); + return false; + } + + /** + * break foo; continue foo; foo: for/while/do + * + * $var: .... + * + * @param label + * @param isDefining + */ + private void addLabel(SimpleName label, boolean isDefining) { + if (label != null) { + buffer.append(' '); + buffer.append(NameMapper.getJavaScriptCollisionIdentifier(label.getIdentifier(), true)); + } + buffer.append(isDefining ? " : " : ";\n"); + } + + public boolean visit(DoStatement node) { + buffer.append("do "); + node.getBody().accept(this); + buffer.append(" while ("); + node.getExpression().accept(this); + buffer.append(");\n"); + return false; + } + + public boolean visit(EmptyStatement node) { + buffer.append(";"); + return false; + } + + public boolean visit(EnhancedForStatement node) { + // for (Integer v : ...) + SimpleName name = node.getParameter().getName(); + ITypeBinding vtype = name.resolveTypeBinding(); + buffer.append("for (var "); + String varName = acceptPossiblyFinalVar(name); + appendReplaceV(", $V = ", "V", varName, null, null); + Expression exp = node.getExpression(); + ITypeBinding eType = exp.resolveTypeBinding(); + ITypeBinding arrayType = eType.getComponentType(); + + if (arrayType == null) { + exp.accept(this); + appendReplaceV(".iterator$(); $V.hasNext$()&&((V=", "V", varName, null, null); + appendReplaceV("($V.next$())", "V", varName, vtype, eType); + appendReplaceV("),1);", "V", varName, null, null); + } else { + appendReplaceV("0, $$V = ", "V", varName, null, null); + exp.accept(this); + appendReplaceV("; $V<$$V.length&&((V=", "V", varName, null, null); + appendReplaceV("($$V[$V])", "V", varName, vtype, arrayType); + appendReplaceV("),1);$V++", "V", varName, null, null); + } + buffer.append(") "); + node.getBody().accept(this); + buffer.append("\n"); + return false; + } + + /** + * For enhanced FOR only. + * + * allow for primitive boxing or unboxing. See test.Test_Chars.java + * + * @param template + * @param v + * @param varName + * @param vType + * @param eType + */ + private void appendReplaceV(String template, String v, String varName, ITypeBinding vType, ITypeBinding eType) { + String s = template.replace(v, varName); + if (vType != eType) { + if (vType.isPrimitive()) { + if (eType.isPrimitive()) { + // this is a conversion of an char[] to an int -- the only + // possibility allowed, I think + s += CHARCODEAT0; + } else { + // So we know the expression is boxed -- Character, for + // example + // Character does not use .objectValue, but we implement it + // here + if (vType.getName().equals("int")) + s += ".intValue$()"; + else + s += ".objectValue$()"; + } + } else if (eType.isPrimitive()) { + // So we know the expression is unboxed -- char, for example + s = "new " + NameMapper.getPrimitiveTYPE(eType.getName()) + s; + } + } + buffer.append(s); + } + + public boolean visit(EnumConstantDeclaration node) { + // see addEnumConstants + return false; + } + + public boolean visit(EnumDeclaration node) { + setPackage(null); + addClassOrInterface(node, node.resolveBinding(), node.bodyDeclarations(), 'e'); + return false; + } + + public boolean visit(ExpressionStatement node) { + // e.g. test.Test_Anon.main(args); + return true; + } + + public void endVisit(ExpressionStatement node) { + buffer.append(";\n"); + } + + @SuppressWarnings("unchecked") + public boolean visit(ForStatement node) { + + buffer.append("for ("); + visitList(node.initializers(), ", "); + buffer.append("; "); + Expression expression = node.getExpression(); + if (expression != null) { + expression.accept(this); + } + buffer.append("; "); + visitList(node.updaters(), ", "); + buffer.append(") "); + node.getBody().accept(this); + buffer.append("\n"); + return false; + } + + public boolean visit(IfStatement node) { + buffer.append("if ("); + appendBoxingNode(node.getExpression(), false); + buffer.append(") "); + node.getThenStatement().accept(this); + Statement ifElse = node.getElseStatement(); + if (ifElse != null) { + buffer.append(" else "); + ifElse.accept(this); + } + return false; + } + + /** + * {....} + * + */ + public boolean visit(Initializer node) { + // handled by addClassOrInterface + return false; + } + + /** + * out: while/for/do... + * + */ + public boolean visit(LabeledStatement node) { + addLabel(node.getLabel(), true); + node.getBody().accept(this); + return false; + } + + public boolean visit(MethodDeclaration node) { + // handled in addClassorInterface + return false; + } + + private final static int METHOD_$_QUALIFIED = 1; + private final static int METHOD_UNQUALIFIED = 2; + private final static int METHOD_FULLY_QUALIFIED = 4; + private final static int METHOD_FULLY_QUALIFIED_JUST_ONE = 12; + + /** + * Called by visit(MethodDeclaration) as well as addLambdaMethod(). + * + * @param mBinding + * @param parameters + * @param body + * @param isConstructor + * @param lambdaType + */ + @SuppressWarnings("null") + private void processMethodDeclaration(IMethodBinding mBinding, List parameters, ASTNode body, + boolean isConstructor, int lambdaType) { + int mods = mBinding.getModifiers(); + boolean isNative = Modifier.isNative(mods); + if (body == null && !isNative && lambdaType == NOT_LAMBDA) { + // Abstract method + return; + } + boolean isPublic = Modifier.isPublic(mods); + boolean isPrivate = !isPublic && !isConstructor && isPrivate(mBinding); + boolean isStatic = isStatic(mBinding); + int qualification = (lambdaType != NOT_LAMBDA ? METHOD_FULLY_QUALIFIED + : temp_add$UnqualifiedMethod ? METHOD_$_QUALIFIED : METHOD_FULLY_QUALIFIED); + if (isUserApplet && lambdaType == NOT_LAMBDA && !isConstructor && !isStatic && isPublic) + qualification |= METHOD_UNQUALIFIED; + String finalName = getFinalMethodNameOrArrayForDeclaration(mBinding, isConstructor, qualification); + boolean isMain = (isStatic && isPublic && mBinding.getName().equals("main") + && mBinding.getKey().indexOf(";.main([Ljava/lang/String;)V") >= 0); + + if (isMain) { + System.out.println(">>>main found for " + class_fullName); + addApplication(); + } + if (global_lstMethodsDeclared != null && !isPrivate) + logMethodDeclared(finalName); + ITypeBinding mClass = mBinding.getDeclaringClass(); + if (isConstructor + && (finalName.equals("'c$'") || mBinding.isVarargs() && mBinding.getParameterTypes().length == 1)) + class_haveDefaultConstructor = true; // in case we are not qualifying + // names here + buffer.append("\nClazz.newMeth(C$, ").append(finalName).append(", function ("); + if (parameters == null) + // lambda method + buffer.append(getLambdaParamList(mBinding, -1)); + else + visitList(parameters, ", "); + buffer.append(") "); + if (lambdaType != NOT_LAMBDA) { + addLambdaBody(body); + if (body == null) + return; + } else if (isConstructor) { + @SuppressWarnings("unchecked") + List statements = ((Block) body).statements(); + ASTNode firstStatement; + if (statements.size() == 0 || !((firstStatement = statements.get(0)) instanceof SuperConstructorInvocation) + && !(firstStatement instanceof ConstructorInvocation)) { + buffer.append("{\n"); + String superclassName = getJavaClassNameSuperNoBrackets(mClass); + if (superclassName == null) { + addCallInit(); + } else { + addSuperConstructor(null, null); + } + visitList(statements, ""); + endVisit((Block) body); + } else { + body.accept(this); + } + } else if (body == null) { + // not a constructor and no body -- native + buffer.append("{\n"); + if (isNative) { + buffer.append("alert('native method must be replaced! " + mBinding.getName() + "');\n"); + log("native: " + mBinding.getName()); + } + buffer.append("}\n"); + } else { + body.accept(this); + } + if (isStatic || isConstructor) + buffer.append(", ").append(isNative ? 2 : 1); + else if (isPrivate) + buffer.append(", " + getPrivateVar(mClass, false)); + buffer.append(");\n"); + } + + /** + * Accept the SimpleName to add it to the buffer, and to get its final form. + * Then create a package_finalVars entry for this name if it is final or + * effectively final. + * + * @param name + * @return the final form of the name + */ + private String acceptPossiblyFinalVar(SimpleName name) { + int pt = buffer.length(); + name.accept(this); + return buffer.substring(pt); + } + + private static boolean isFinalOrEffectivelyFinal(IBinding binding) { + return Modifier.isFinal(binding.getModifiers()) + || binding instanceof IVariableBinding && ((IVariableBinding) binding).isEffectivelyFinal(); + } + + public boolean visit(MethodInvocation node) { + addMethodInvocation(node.getName(), node.arguments(), node.resolveMethodBinding(), node.getExpression(), -1); + return false; + } + + /** + * Called by visit(MethodDeclaration) as well as addLambdaMethod(). + * + * @param javaQualifier + * @param arguments + * @param mBinding + * @param expression + */ + private boolean addMethodInvocation(SimpleName javaQualifier, List arguments, IMethodBinding mBinding, + Expression expression, int lambdaArity) { + if (javaQualifier == null) { + // not possible? + dumpStack("addMethodInvocation null simpleJavaName " + mBinding + " " + expression + " " + lambdaArity); + } + String methodName = mBinding.getName(); + ITypeBinding declaringClass = mBinding.getDeclaringClass(); + boolean isStatic = isStatic(mBinding); + boolean isPrivate = isPrivate(mBinding); + boolean isPrivateAndNotStatic = isPrivate && !isStatic; + String privateVar = (isPrivateAndNotStatic ? getPrivateVar(declaringClass, false) : null); + boolean doLogMethodCalled = (!isPrivate && global_htMethodsCalled != null); + boolean needBname = (!isStatic && lambdaArity < 0 && (expression == null + ? !areEqual(declaringClass, class_typeBinding) + && !class_typeBinding.isAssignmentCompatible(declaringClass) + : expression instanceof ThisExpression && ((ThisExpression) expression).getQualifier() != null)); + String bname = (needBname ? getThisRefOrSyntheticReference(javaQualifier, declaringClass, null) : null); + // add the qualifier + int pt = buffer.length(); + if (isPrivateAndNotStatic) { + // note that the following expression will not work if the method is private: + // (b ? classA : classB).method() + // because we don't know at compile time which class is being run. + buffer.append(privateVar); + buffer.append("."); + } else if (lambdaArity >= 0) { + doLogMethodCalled = false; + } else if (expression == null) { + doLogMethodCalled = false; + if (bname != null) { + buffer.append(bname); + buffer.append("."); + } else if (!isStatic) { + buffer.append("this."); + } else { + // this will be C$., I think. + } + } else { + appendFinalMethodQualifier(expression, declaringClass, bname, + (isStatic && !isPrivate ? FINAL_ESCAPECACHE : FINAL_CACHE) + | (isStatic ? FINAL_STATIC : 0)); + buffer.append("."); + } + + // keep a pointer, because we may rewrite this + int ptLog = (doLogMethodCalled ? buffer.length() : 0); + + // check for special Clazz.array or Clazz.forName + // as well as special treatment for String.indexOf and String.lastIndexOf + + boolean isSpecialMethod = false; + boolean isIndexOf = false; + String declaringClassJavaClassName = getJavaClassNameQualified(declaringClass); + + if (lambdaArity < 0 && !isPrivateAndNotStatic) { + String j2sName = NameMapper.getJ2SFinalMapClazzMethod(declaringClassJavaClassName, methodName); + bufferDebug(">>>" + j2sName + " " + declaringClassJavaClassName + " " + methodName); + if (j2sName != null) { + isSpecialMethod = true; + // overwrite qualifier + buffer.setLength(pt); + buffer.append(j2sName); + doLogMethodCalled = false; + bname = null; + } else if (declaringClassJavaClassName.equals("java.lang.String") + && (methodName.equals("indexOf") || methodName.equals("lastIndexOf"))) { + // indicate to boxer method to use method "q" not "p" here. + // This allows characters to be left as strings in String.indexOf$I and + // String.lastIndexOf$I for faster processing. + isIndexOf = true; + } + } + + // have xxxx. + + // now get the method name + + // record whether this.b$[.....] was used, and if so and it is private, + // we need to use it again + String term = ")"; + if (!isSpecialMethod) { + // TODO: for now we are just returning name$ for all lambda methods + // note that simpleNameInMethodBinding can return C$.xxxx + + String j2sName = getFinalDotQualifiedNameForMethod(javaQualifier, mBinding, + (expression == null ? METHOD_NULLEXPRESSION : 0) | METHOD_ISQUALIFIED + | (lambdaArity >= 0 ? LAMBDA_METHOD : 0) | (isStatic ? FINAL_STATIC : 0)); + + String finalMethodNameWith$Params = getFinalMethodNameWith$Params(j2sName, declaringClassJavaClassName, + mBinding, null, true, METHOD_NOTSPECIAL); + + if (lambdaArity >= 0) { + // The problem here is that we cannot apply a method from an interface + // because those methods are not present in JavaScript. + // Otherwise we would use $$.prototype here. The assumption + // in using "t.apply" is that t must be non-null. + // if this does not work, then we can go to ($$.prototype || t) + // + boolean classIsTarget = (class_localType == LAMBDA_WRAPPED && ( + (isStatic + || expression instanceof ClassInstanceCreation + || isVariableBinding(expression) + || expression instanceof ThisExpression + || "java.lang.Class".equals(removeBracketsAndFixNullPackageName(declaringClassJavaClassName)) // String.class::cast + ) // BH Added 2019.05.13 + && lambdaArity == mBinding.getParameterTypes().length)); + String opening = (classIsTarget ? "$$." : "t.") + finalMethodNameWith$Params + ".apply(" + + (isStatic ? "null" : classIsTarget ? "$$" : "t") + ",["; + buffer.append(opening); + buffer.append(getLambdaParamList(mBinding, lambdaArity)); + buffer.append("])"); + return isStatic; + } + if (finalMethodNameWith$Params.indexOf('|') >= 0) { + // cover multiple parameter options to cover older versions of java + // foo.xx$T$K || $o$.xx$O$O --> ($o$=foo).($o$.xx$T$K || + // $o$.xx$O$O) + doLogMethodCalled = false; + postFixGeneric$OMethodName(pt, finalMethodNameWith$Params, isPrivateAndNotStatic, privateVar); + term = "])"; + } else { + // standard call + buffer.append(finalMethodNameWith$Params); + } + + if (doLogMethodCalled) { + String name = declaringClassJavaClassName + "." + buffer.substring(ptLog); + logMethodCalled(name); + } + if (isPrivateAndNotStatic || bname != null) { + // A call to a nonprivate outer-class method from an inner class + // requires using apply on the object, which is either + // this or this.b$["....."] + // + // all private calls are made to var p$x + + buffer.append(".apply("); + appendFinalMethodQualifier(expression, declaringClass, bname, FINAL_CACHE); + buffer.append(", ["); + term = "])"; + } + } + if (term == ")") + buffer.append("("); + addMethodParameterList(arguments, mBinding, null, null, isIndexOf ? METHOD_INDEXOF : METHOD_NOTSPECIAL); + buffer.append(term); + return true; + } + + /** + * Write the method expression to the buffer. If the qualifier is a variable, + * such as System.out, or an expression, such as (useF? f : g), then we can + * simply accept it. Othewise we need to qualify it. + * + * @param qualifier null for "this" or outer-class reference; + * + * @param declaringClass + * @param bname + * @param flags FINAL_LAMBDA | FINAL_ESCAPE | FINAL_CACHE + */ + private void appendFinalMethodQualifier(Expression qualifier, ITypeBinding declaringClass, String bname, + int flags) { + if (qualifier == null) { + if ((flags & FINAL_LAMBDA) != 0) { + buffer.append(getFinalJ2SClassNameQualifier(null, declaringClass, null, flags)); + } else { + buffer.append(bname == null ? "this" : bname); + } + } else if (qualifier instanceof Name && !isVariableBinding(qualifier)) { + buffer.append(getFinalJ2SClassNameQualifier((Name) qualifier, declaringClass, null, flags)); + } else if ((flags & FINAL_STATIC) != 0) { + // ensure even if field.method(), as long as method is static, we use Class.method() + // otherwise a null value for field will throw an exception. + buffer.append(getFinalJ2SClassNameQualifier(null, declaringClass, null, flags)); + } else { + // xxxx.field.foo() -- but only if foo is not static + // (x ? y : z).foo() + // xxx.this.foo() + qualifier.accept(this); + } + } + + private static boolean isVariableBinding(Expression qualifier) { + return qualifier instanceof Name && ((Name) qualifier).resolveBinding() instanceof IVariableBinding; + } + + public boolean visit(ReturnStatement node) { + buffer.append("return"); + Expression expression = node.getExpression(); + if (expression != null) { + buffer.append(' '); + + ASTNode parent = node.getParent(); + while (parent != null && !(parent instanceof MethodDeclaration)) { + parent = parent.getParent(); + } + IMethodBinding mBinding = (parent == null ? null : ((MethodDeclaration) parent).resolveBinding()); + ITypeBinding retType = (mBinding == null ? null : mBinding.getReturnType()); + addExpressionAsTargetType(expression, retType, "r", null); + } + buffer.append(";\n"); + return false; + } + + /** + * method parameters or catch variables + */ + public boolean visit(SingleVariableDeclaration node) { + acceptPossiblyFinalVar(node.getName()); + return false; + } + + public boolean visit(SuperConstructorInvocation node) { + IMethodBinding constructorBinding = node.resolveConstructorBinding(); + if (constructorBinding != null && !isObjectOrNull(constructorBinding.getDeclaringClass())) { + addSuperConstructor(node, constructorBinding.getMethodDeclaration()); + } else { + addCallInit(); + } + return false; + } + + public boolean visit(SuperMethodInvocation node) { + IMethodBinding mBinding = node.resolveMethodBinding(); + String finalMethodNameWith$Params = getFinalMethodNameWith$Params(null, null, mBinding, null, false, + METHOD_NOTSPECIAL); + // BH if this is a call to super.clone() and there is no superclass, or + // the superclass is Object, + // then we need to invoke Clazz.clone(this) directly instead of calling + // C$.superclazz.clone() + if ("clone$".equals(finalMethodNameWith$Params) && getJavaClassSuper(mBinding.getDeclaringClass()) == null) { + buffer.append("Clazz.clone(this)"); + } else { + buffer.append("C$.superclazz.prototype." + finalMethodNameWith$Params + ".apply(this, "); + buffer.append("["); + addMethodParameterList(node.arguments(), mBinding, null, null, METHOD_NOTSPECIAL); + buffer.append("])"); + } + + return false; + } + + @SuppressWarnings("unchecked") + public boolean visit(SwitchStatement node) { + buffer.append("switch ("); + addNonCharacter(node.getExpression()); + buffer.append(") {\n"); + visitList(node.statements(), ""); + buffer.append("}\n"); + return false; + } + + public boolean visit(SwitchCase node) { + if (node.isDefault()) { + buffer.append("default"); + } else { + buffer.append("case "); + addNonCharacter(node.getExpression()); + } + buffer.append(":\n"); + return false; + } + + public boolean visit(SynchronizedStatement node) { + // we could wrap this with a simple if() statement, + // checking that it is not null, but that seems to me + // to be unnecessary. When would one ever intentionally + // produce a null pointer exception from synchronized(...)? + + Expression e = node.getExpression(); + if (!(e instanceof Name || e instanceof TypeLiteral || e instanceof ThisExpression)) { + buffer.append("/*sync " + e.getClass().getName() + "*/"); + // get actual JavaScript code + int pt = buffer.length(); + e.accept(this); + String expr = buffer.substring(pt, buffer.length()); + buffer.setLength(pt); + // ignore (treeLock()) + if (!(e instanceof MethodInvocation && expr.indexOf(".getTreeLock()") >= 0)) { + buffer.append("("); + buffer.append(expr); + buffer.append(");\n"); + } + } + node.getBody().accept(this); + return false; + } + + public boolean visit(ThrowStatement node) { + buffer.append("throw "); + node.getExpression().accept(this); + buffer.append(";\n"); + return false; + } + + @SuppressWarnings({ "unchecked", "null" }) + public boolean visit(TryStatement node) { + List catchClauses = node.catchClauses(); + int size = catchClauses.size(); + Block finallyBlock = node.getFinally(); + List resources = node.resources(); + // Returns the live ordered list of resources for this try statement (added in + // JLS4 API). + // [ooh...JSL9 change...] + // A resource is either a VariableDeclarationExpression or (since JLS9) a Name. + int pt = -1; + if (resources != null && resources.size() > 0 ) { + buffer.append("try {\n"); + pt = buffer.length(); + } + buffer.append(size > 0 || finallyBlock != null ? "try " : "/*try*/ "); + node.getBody().accept(this); + if (size > 0) { + String catchEName = "e$$"; + if (size == 1) { + CatchClause element = catchClauses.get(0); + SimpleName exName = element.getException().getName(); + catchEName = exName.getIdentifier(); + } + buffer.append(" catch (" + catchEName + ") "); + boolean scopeAdded = false; + boolean endedWithThrowable = false; + for (Iterator iter = catchClauses.iterator(); iter.hasNext();) { + CatchClause element = iter.next(); + List types; + Type type = element.getException().getType(); + if (type instanceof UnionType) { + types = ((UnionType) type).types(); + } else { + (types = new ArrayList()).add(type); + } + boolean haveType = false; + for (int j = 0; j < types.size(); j++) { + type = types.get(j); + if ("java.lang.Throwable".equals(type.resolveBinding().getQualifiedName())) { + endedWithThrowable = true; + } else { + if (!scopeAdded) { + buffer.append("{\n"); + scopeAdded = true; + } + buffer.append(haveType ? " || " : "if ("); + buffer.append("Clazz.exceptionOf(" + catchEName + ",\""); + buffer.append(getFinalJ2SClassName(getClassJavaNameForType(type), FINAL_RAW)); + buffer.append("\")"); + haveType = true; + } + } + if (haveType) + buffer.append(")"); + SimpleName exName = element.getException().getName(); + String eName = exName.getIdentifier(); + boolean notEName = false; + if (!catchEName.equals(eName)) { + buffer.append("{\nvar " + eName + " = " + catchEName + ";\n"); + notEName = true; + } + element.getBody().accept(this); + if (notEName) { + buffer.append("\n}"); + } + if (iter.hasNext()) { + buffer.append(" else "); + } + } + if (!endedWithThrowable) { + buffer.append(" else {\nthrow " + catchEName + ";\n}"); + } + if (scopeAdded) { + buffer.append("\n}"); + } + } + if (finallyBlock != null) { + buffer.append(" finally "); + finallyBlock.accept(this); + } + if (pt >= 0) { + // just after first "{" + String buf = buffer.substring(pt); + buffer.setLength(pt); + String closing = ""; + for (int i = 0; i < resources.size(); i++) { + ASTNode resource = resources.get(i); + pt = buffer.length(); + resource.accept(this); + buffer.append(";\n"); + closing = getResourceClosing(pt) + closing; + } + buffer.append(buf); + buffer.append("\n}finally{/*res*/").append(closing).append("}"); + } + buffer.append("\n"); + return false; + } + + private String getResourceClosing(int pt) { + String name = buffer.substring(pt); + // Java 9 try(res) or Java 8 try(OutputStream os = ....) + if ((pt = name.indexOf("=")) >= 0 || + (pt = name.indexOf(";")) >= 0) { + name = name.substring(0, pt); + } + if (name.startsWith("var ")) + name = name.substring(4); + return "\ntry{" + name + "&&" + name + ".close$&&"+ name + ".close$()}catch(_){}"; + } + + /** + * A class or interface is being declared. + * + * + */ + public boolean visit(TypeDeclaration node) { + setPackage(null); + // anonymous will never come through here. It will be routed directly to + // addClassOrInterface + List declarations = node.bodyDeclarations(); + ITypeBinding binding = node.resolveBinding(); + addClassOrInterface(node, binding, declarations, + node.isInterface() ? 'i' : node.isLocalTypeDeclaration() ? 'l' : 'c'); + return false; + } + + /** + *
+	 * VariableDeclarationStatement:
+	 *    { ExtendedModifier } Type VariableDeclarationFragment
+	 *        { , VariableDeclarationFragment } ;
+	 * 
+ */ + public boolean visit(VariableDeclarationStatement node) { + + @SuppressWarnings("unchecked") + List fragments = node.fragments(); + for (Iterator iter = fragments.iterator(); iter.hasNext();) { + buffer.append("var "); + ASTNode next = iter.next(); + next.accept(this); + buffer.append(";\n"); + } + return false; + } + + public boolean visit(WhileStatement node) { + + buffer.append("while ("); + node.getExpression().accept(this); + buffer.append(")"); + node.getBody().accept(this); + buffer.append("\n"); + return false; + } + + ////////// END visit/endVisit /////////// + + private void addAnonymousFunctionWrapper(boolean isOpen) { + buffer.append( + isOpen ? (buffer.lastIndexOf(")") >= buffer.length() - 3 ? ";" : "") + "\n(function(){" : "})()\n"); + } + + /** + * The call to C$.$init$ from a constructor is made in two cases: + * + * a) as the second statement in a constructor, when the first line is a + * super(...) call + * + * b) as the first statement in a constructor that does not call super(...) or + * this(...) (because the superclass is Object) + * + * Note that it is not called when the first statement is this(...). + * + */ + private void addCallInit() { + buffer.append(";C$.$init$.apply(this);\n"); + } + + private void appendClinit() { + buffer.append("\nC$.$clinit$=2;\n"); + } + + /** + * Add Clazz.newInterface(...) or Clazz.newClass(...) for all classes and + * interfaces, including Enum and anonymous. + * + * If this is an inner class, then iterate, just adding its definition to the + * current staticBuffer; + * + * @param node + * @param binding + * @param BodyDeclarations + * @param type 'a' (anonymous class), + * 'e' (Enum), + * 'i' (Interface), + * 'l' (local), + * 'm' (LambdaExpression), + * '@' (_at_interface AnnotationType), + * or 'c' (standard + * class) + * @return localName + */ + @SuppressWarnings({ "null", "unchecked" }) + private boolean addClassOrInterface(ASTNode node, ITypeBinding binding, List bodyDeclarations, char type) { + if (binding == null) + return false; + + checkGenericClass(binding, binding); + + ASTNode parent = node.getParent(); + + boolean isEnum = (type == 'e'); + boolean isInterface = (type == 'i'); + boolean isAnnotation = (type == '@'); + boolean isTrulyLocal = (type == 'l'); + boolean isLambda = (type == 'm'); + boolean isAnonymous = (type == 'a' || isLambda); + boolean isClass = (type == 'c'); + boolean isTopLevel = (!isLambda && binding.isTopLevel()); + + if (!isTopLevel && !isAnonymous && node != innerNode) { + // inner named class first pass only + + // includes EnumDeclaration + + // We use a temporary visitor to put the code + // into a temporary buffer, produce the code in the + // temporary visitor, append it to our TrailingBuffer, + // and return. + + // get the visitor + + Java2ScriptVisitor tempVisitor = null; + try { + tempVisitor = getClass().newInstance().setProject(global_project, global_testing).setInnerGlobals(this, + node); + } catch (@SuppressWarnings("unused") Exception e) { + // impossible + } + + // set its className and binding + + String className; + if (parent instanceof TypeDeclarationStatement) { + String anonClassName = removeBrackets(getJavaClassNameQualified(binding)); + className = anonClassName.substring(anonClassName.lastIndexOf('.') + 1); + // note - this className is NOT QUALIFIED + } else { + className = class_shortName + "." + binding.getName(); + } + tempVisitor.setClassAndBinding(className, binding); + + // generate the code + + if (isTrulyLocal) { + tempVisitor.addInnerDeclaration(node, binding, NOT_LAMBDA, true, bodyDeclarations); + } else { + tempVisitor.addClassOrInterface(node, binding, bodyDeclarations, type); + } + + // append it to our TrailingBuffer + + trailingBuffer.append(tempVisitor.buffer.toString()); + + return false; + } + + // set up key fields and local variables + + if (isTrulyLocal || isClass || isEnum) { + checkAnnotations((BodyDeclaration) node, CHECK_ANNOTATIONS_ONLY); + } + ITypeBinding oldBinding = null; + String oldShortClassName = null, this$0Name0 = null, finalShortClassName, finalPackageName; + if (isTopLevel) { + String javaName = binding.getName(); + appendElementKey(javaName); + setClassAndBinding(javaName, binding); + if (isClass) + isUserApplet = checkAddApplet(binding); + } + if (isAnonymous) { + oldShortClassName = class_shortName; + oldBinding = class_typeBinding; + // anonymous classes reference their package, not their outer class in + // Clazz.newClass, so clazz.$this$0 is not assigned. + this$0Name0 = this$0Name; + this$0Name = null; + finalShortClassName = getFinalJ2SClassName( + (isLambda ? getMyJavaClassNameLambda(true) : getJavaClassNameQualified(binding)), FINAL_P); + if (finalShortClassName.startsWith("P$.")) { + // java.lang.x will return x, not P$.x + finalShortClassName = finalShortClassName.substring(3); + } + setClassAndBinding(finalShortClassName, binding); + if (isLambda) { + buffer.append("("); + + // problem here 2019.12.07 cifbinary was that $$-wrapped lambda methods must NOT be reused. + + } + buffer.append("("); + if (!isLambda || class_localType != LAMBDA_WRAPPED) + buffer.append("P$." + finalShortClassName + "||"); + finalPackageName = "P$"; + } else { + // Top or inner named classes are already set. + // For inner classes, we are just loading up the temporary buffer. + // The code will end up in the trailing buffer for the parent class. + int pt1 = class_fullName.lastIndexOf('.'); + finalShortClassName = class_fullName.substring(pt1 + 1); + finalPackageName = checkPackageP$Name(class_fullName.substring(0, pt1)); + } + + // add the anonymous wrapper if needed + + if (!isTopLevel) { + addAnonymousFunctionWrapper(true); + } + + // begin the class or interface definition + + buffer.append("/*" + type + //+ "=" + class_typeBinding.getKey() + + "*/"); + buffer.append("var C$=" + (isInterface ? "Clazz.newInterface(" : "Clazz.newClass(")); + + // arg1 is the package name + // arg2 is the full class name in quotes + // arg3 is the class definition function, C$, which is called in + // Clazz.new_(). + // arg4 is the superclass + // arg5 is the superinterface(s) + // arg6 is the type: anonymous(1), local(2), or absent + + // Here we go... + + // arg1: package name or null + // arg2: shortened class name in quotes + + buffer.append(finalPackageName + ", \"" + finalShortClassName + "\""); + + // set up func, superclass, and superInterface + + String func = "null"; + List superInterfaceTypes = null; + + // arg3: class definition function, C$, or null to add the standard + // function at run time + + boolean hasDependents = isEnum; + buffer.append(", "); + List unqualifiedMethods = getUnqualifiedMethods(binding, null); + List innerClasses = new ArrayList<>(); + if (isAnonymous) { + if (!(parent instanceof EnumConstantDeclaration)) + func = "function(){Clazz.newInstance(this, arguments[0],1,C$);}"; + ITypeBinding[] declaredTypes = null; + if (isLambda) { + // we just have this one superinterface, which is the lambda binding itself + declaredTypes = new ITypeBinding[] { binding }; + } else { + declaredTypes = binding.getInterfaces(); + } + if (declaredTypes != null && declaredTypes.length > 0) { + List types = new ArrayList(); + ITypeBinding anonClassImplemented = declaredTypes[0]; + types.add(anonClassImplemented); + superInterfaceTypes = types; + } + } else { + for (Iterator iter = bodyDeclarations.iterator(); iter.hasNext();) { + BodyDeclaration bd = (BodyDeclaration) iter.next(); + if (bd instanceof TypeDeclaration || bd instanceof EnumDeclaration || bd instanceof AnnotationTypeDeclaration) { + innerClasses.add((AbstractTypeDeclaration) bd); + } + } + if (!isTopLevel || !innerClasses.isEmpty()) { + func = null; + buffer.append("function(){\n"); + + // add all inner classes iteratively + + for (int i = 0; i < innerClasses.size(); i++) + innerClasses.get(i).accept(this); + + // continue with Clazz.newClass... + // add the Clazz.newInstance call, which: + // (a) adds .valueOf() = function() {return this} for Number + // subclasses + // (b) sets objThis.__JSID__ to a unique number + // (c) handles inner class final variables + // (d) includes a call to the constructor c$() when called + // directly by the user using new Foo() + if (!isInterface) { + buffer.append("Clazz.newInstance(this, arguments") + .append(isTopLevel ? ",0" : "[0]," + !isStatic(binding)).append(",C$);\n"); + } + buffer.append("}"); + } + if (isEnum) { + superInterfaceTypes = ((EnumDeclaration) node).superInterfaceTypes(); + } else if (!isAnnotation) { + superInterfaceTypes = ((TypeDeclaration) node).superInterfaceTypes(); + } + + } + + if (func != null) + buffer.append(func); + + // arg4: superclass or null + + buffer.append(", "); + ITypeBinding superclass; + if (isEnum) { + buffer.append("'Enum'"); + } else if (isLambda || (superclass = getJavaClassSuper(binding)) == null) { + buffer.append("null"); + } else { + hasDependents = true; + String superclassName = getJavaClassNameQualified(superclass); + if (superclassName == null || superclassName.length() == 0 || "java.lang.Object".equals(superclassName)) { + buffer.append("null"); + } else { + if (isAnonymous) { + buffer.append(getFinalJ2SClassNameQualifier(null, superclass, superclassName, FINAL_ESCAPE)); + } else { + buffer.append(getFinalInnerClassList(superclass, superclassName)); + } + } + } + + // arg5: superinterface(s) if not null + + if (superInterfaceTypes != null && superInterfaceTypes.size() > 0) { + hasDependents = true; + buffer.append(", "); + String term = ""; + if (superInterfaceTypes.size() > 1) { + buffer.append("["); + term = "]"; + } + String sep = ""; + + for (Iterator iter = superInterfaceTypes.iterator(); iter.hasNext();) { + buffer.append(sep); + Object iface = iter.next(); + ITypeBinding ibinding = (iface instanceof Type ? ((Type) iface).resolveBinding() + : (ITypeBinding) iface); + String term1 = ""; + if (!ibinding.isTopLevel()) { + if (sep == "" && term == "") { + buffer.append("["); + term = "]"; + } + buffer.append("["); + term1 = "]"; + ITypeBinding b = ibinding; + int pt = buffer.length(); + while (!b.isTopLevel()) { + b = b.getDeclaringClass(); + buffer.insert(pt, "'" + getFinalJ2SClassName(getJavaClassNameQualified(b), FINAL_RAW) + "',"); + } + } + buffer.append("'"); + buffer.append(getFinalJ2SClassName(getJavaClassNameQualified(ibinding), FINAL_RAW)); + buffer.append("'"); + buffer.append(term1); + sep = ", "; + } + buffer.append(term); + } else if (isAnnotation) { + hasDependents = true; + buffer.append(", 'java.lang.annotation.Annotation'"); + } else { + buffer.append(", null"); + } + + // arg6: anonymous(1), local(2), or absent + + if (isTrulyLocal) { + buffer.append(", 2"); + } else if (isAnonymous) { + buffer.append(", 1"); + } else { + // remove excessive null parameters + int i; + while (", null".equals(buffer.substring(i = buffer.length() - 6))) + buffer.setLength(i); + } + + // close the initializer + + buffer.append(");\n"); + + // add the Java8 compatibility local variable $o$ + + // also add the local var p$ short for C$.prototype if we have any + // private methods + + // add all the methods + + TrailingBuffer oldTrailingBuffer = trailingBuffer; + trailingBuffer = new TrailingBuffer(); + + // for annotations: + boolean haveFieldMethodAnnotations = false; + + FieldInfo fieldInfoOld = fieldInfo; + boolean haveFieldInfoOld = haveFields; + fieldInfo = new FieldInfo(); + + /** + * the static initializer + */ + List lstStatic = new ArrayList(); + if (!isLambda) { + // get the list of static methods and check for annotations + for (Iterator iter = bodyDeclarations.iterator(); iter.hasNext();) { + BodyDeclaration element = (BodyDeclaration) iter.next(); + boolean isField = element instanceof FieldDeclaration; + boolean isMethod = element instanceof MethodDeclaration; + IMethodBinding b = null; + if (!haveFieldMethodAnnotations && isMethod + && (b = ((MethodDeclaration) element).resolveBinding()) != null && b.getAnnotations().length > 0) { + haveFieldMethodAnnotations = true; + } + if (isField || element instanceof Initializer) { + if ((isInterface || isStatic(element)) && checkAnnotations(element, CHECK_J2S_IGNORE_ONLY)) { + lstStatic.add(element); + } + } + } + } + + List enums = (isEnum ? new ArrayList<>() : null); + List fields = (haveFieldMethodAnnotations || isInterface || isLambda || isEnum ? null : new ArrayList<>()); + List methods = (haveFieldMethodAnnotations || isAnnotation || fields != null ? new ArrayList<>() : null); + + if (hasDependents) { + // Add the class static initializer C$.$clinit$(), which in SwingJS will trigger + // loading of all superclasses and interfaces, and set up the prototype + // correctly. It does not initialize fields. + // + // C$.$clinit$ is set to 0 immediately when run so that it is run + // just once per class. (In contrast, C$.$init$ is run once per instance.) + // just "$clinit$=1;" now; this will be replaced by SwingJS + // at runtime with a call to Clazz.load(C$,1) + + // Prior to 3.2.4.10, $clinit$ was also doing static initialization. But that + // turns out not to be quite right. Static intitialization (now in $static$) + // for a set of related classes must be done after all are loaded, as otherwise + // the superclass and interface methods may not be added to the subclass prototype. + // Only found (to date) only in biojava where the interface Location has a static + // initializer for one of its implementing classes. Very tricky! + + appendClinit(); + } + if (lstStatic.size() > 0 || isEnum) { + // create $static$ (Java's ) + + // All static fields that have initializers must be introduced first + // as null if Objects and then finalized in $static$(), + // even if they are their default values. This is because they might have been + // modified by other actions between the time they were initially initialized + // and when $static$ is run. This happens when the static fields in class A + // reference static fields in class B, which in turn reference static fields + // in Class A. + + int pt = buffer.length(); + buffer.append("\nC$.$static$ = function() {C$.$static$=0;\n"); + boolean haveDeclarations = isEnum; + if (isEnum) + addEnumConstants((EnumDeclaration) node, enums); + for (int i = lstStatic.size(); --i >= 0;) { + BodyDeclaration element = lstStatic.remove(0); + if (element instanceof Initializer) { + ((Initializer) element).getBody().accept(this); + buffer.append(";\n"); + haveDeclarations = true; + } else if (addFieldDeclaration((FieldDeclaration) element, true)) { + haveDeclarations = true; + } + } + if (haveDeclarations) + buffer.append("}\n"); + else + buffer.setLength(pt); + } + + if (isAnonymous) { + setClassAndBinding(finalShortClassName, binding); + } + + if (!isInterface) { + + // if this is not an interface, generate $init$ method, which declares nonstatic fields that have initializers and + // is called just after the call to a superconstructor. + + buffer.append("\nClazz.newMeth(C$, '$init$', function () {\n"); // C$.$load$&&Clazz.load(C$,2); + // we include all field definitions here and all nonstatic + // initializers + + if (!isLambda) + for (Iterator iter = bodyDeclarations.iterator(); iter.hasNext();) { + BodyDeclaration element = (BodyDeclaration) iter.next(); + boolean isField = element instanceof FieldDeclaration; + if ((isField || element instanceof Initializer) && !isStatic(element) + && checkAnnotations(element, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) { + if (isField) { + addFieldDeclaration((FieldDeclaration) element, false); + if (class_annotations != null && fields == null) + fields = new ArrayList<>(); + if (fields != null && !Modifier.isTransient(((FieldDeclaration) element).getModifiers())) + fields.add((FieldDeclaration) element); + } else { + ((Initializer) element).getBody().accept(this); + buffer.append("\n"); + } + } + } + buffer.append("}, 1);\n"); + + } + + if (haveFields) + fieldInfo.add$fields$(); + fieldInfo = fieldInfoOld; + haveFields = haveFieldInfoOld; + + + // add all the methods + + StringBuffer defaults = new StringBuffer(); + + boolean isStatic = true; + if (isLambda) { + isStatic = addLambdaClass(node, binding.getFunctionalInterfaceMethod()); + } else { + if (isAnnotation) { + buffer.append("C$.prototype.annotationType = function() { return this.getClass$() };\n"); + trailingBuffer.append("C$.$getMembers$ = function() { var a=[];\n"); + } + for (Iterator iter = bodyDeclarations.iterator(); iter.hasNext();) { + ASTNode element = (ASTNode) iter.next(); + if (element instanceof MethodDeclaration) { + MethodDeclaration mnode = (MethodDeclaration) element; + IMethodBinding method = mnode.resolveBinding(); + if (method == null || !checkAnnotations(mnode, CHECK_J2S_IGNORE_AND_ANNOTATIONS)) + continue; + + if (methods != null) { + String mname = method.getName(); + if (class_annotationType == NOT_JAXB + || mname.startsWith("set") + || mname.startsWith("get") + || mname.startsWith("is")) + methods.add(method); + } + int defpt = -1; + if (Modifier.isDefault(method.getModifiers())) { + log("default method " + method.getKey()); + defpt = buffer.length(); + } + boolean addUnqualifiedCurrent = temp_add$UnqualifiedMethod; + if (unqualifiedMethods != null) { + // check for all methods that override a functional interface abstract method, + // as those methods are to be qualified only with $ + + for (int i = unqualifiedMethods.size(); --i >= 0;) { + if (method.overrides(unqualifiedMethods.get(i))) { + temp_add$UnqualifiedMethod = true; + break; + } + } + } + processMethodDeclaration(method, mnode.parameters(), mnode.getBody(), mnode.isConstructor(), + NOT_LAMBDA); + if (defpt >= 0) { + defaults.append(buffer.substring(defpt)); + buffer.setLength(defpt); + } + temp_add$UnqualifiedMethod = addUnqualifiedCurrent; + } else if (element instanceof AnnotationTypeMemberDeclaration) { + processAnnotationTypeMemberDeclaration((AnnotationTypeMemberDeclaration) element); + } + } + if (isAnnotation) { + trailingBuffer.append("return a}"); + } + } + + if (isInterface) { + + // Check for static type declarations in interfaces + // This will create a new visitor. + // Static field buffer may be filled with contents. + + for (Iterator iter = bodyDeclarations.iterator(); iter.hasNext();) { + ASTNode element = (ASTNode) iter.next(); + if (element instanceof TypeDeclaration) + element.accept(this); + } + + if (defaults.length() > 0) { + buffer.append("C$.$defaults$ = function(C$){\n").append(defaults).append("};"); + } + } + + // add any recently defined static field definitions, assert strings + // and Enum constants + + if (class_annotations != null) { + // lambda expressions may have an enclosing annotation type, but they will not have methods + ClassAnnotation.addClassAnnotations(this, class_annotationType, class_annotations, enums, fields, methods, + innerClasses, trailingBuffer); + class_annotations = null; + class_annotationType = ANNOTATION_TYPE_UNKNOWN; + //class_hasTypeAnnotations = false; + } + + buffer.append(trailingBuffer); // also writes the assert string + if (isAnonymous) { + // if anonymous, restore old static def buffer + trailingBuffer = oldTrailingBuffer; + } else { + // otherwise, dump the oldStatic buffer and start a new one + buffer.append(oldTrailingBuffer); + trailingBuffer = new TrailingBuffer(); + if (!isInterface) + addDefaultConstructor(); + if (isEnum) { + buffer.append("var $vals=[];\n"); + // implicit Enum methods added as trailer + buffer.append("Clazz.newMeth(C$, 'values$', function() { return $vals }, 1);\n"); + buffer.append( + "Clazz.newMeth(C$, 'valueOf$S', function(name) { for (var val in $vals){ if ($vals[val].name == name) return $vals[val]} return null }, 1);\n"); + } + } + + getJ2sJavadoc(node, DOC_ADD_POST); + + if (!isTopLevel) { + addAnonymousFunctionWrapper(false); + if (isAnonymous) { + buffer.append(")"); + this$0Name = this$0Name0; + setClassAndBinding(oldShortClassName, oldBinding); + } + } + return isStatic; + } + +// private boolean checkDeclarationType(BodyDeclaration element, int type) { +// switch (class_jaxbAccessorType) { +// case JAXB_TYPE_FIELD: +// case JAXB_TYPE_PROPERTY: +// return (type == class_jaxbAccessorType); +// case JAXB_TYPE_NONE: +// case JAXB_TYPE_PUBLIC_MEMBER: +// return true; +// // can't check here, because if just one of set or get is annotated, +// // then it doesn't matter if either is public or not +//// return Modifier.isPublic(element.getModifiers()); +// default: +// case JAXB_TYPE_UNKNOWN: +// return false; +// } +// } + + private void processAnnotationTypeMemberDeclaration(AnnotationTypeMemberDeclaration node) { + + Expression def = node.getDefault(); + SimpleName name = node.getName(); + IMethodBinding mbinding = node.resolveBinding(); + ITypeBinding ret = mbinding.getReturnType(); +// buffer.append("\nClazz.newMeth(C$,'").append(name).append("',function(){});\n"); + int pt = buffer.length(); +// retName = j2sClassObject(ret, retName); +// if (ret.isPrimitive()) { +// retName = NameMapper.getPrimitiveTYPE(retName) + ".TYPE"; +// } else { +// retName += ".class"; +// } + buffer.append("a.push(['" + name + "'," + j2sClassObject(ret) + ","); + if (def == null) { + if (ret.isPrimitive()) { + switch (ret.getName()) { + case "char": + buffer.append("'\0'"); + break; + case "boolean": + buffer.append("false"); + break; + default: + buffer.append("0"); + break; + } + } else { + //if (mbinding.getAnnotations() == null) { + buffer.append("null"); + //} else { + // buffer.append("\"" + mbinding.getAnnotations() + "\""); + //} + } + } else if (ret.isAnnotation()){ + buffer.append("'@" + getFinalJ2SClassName(getJavaClassNameQualified(def.resolveTypeBinding()), FINAL_RAW) + "'"); + } else { + def.accept(this); + } + buffer.append("]);\n"); + trailingBuffer.append(buffer.substring(pt)); + buffer.setLength(pt); + } + + private String getPackageName(ITypeBinding javaClass) { + IPackageBinding p = javaClass.getPackage(); + return (p == null ? "_" : p.getName()); + } + + /** + * Collect all names of all functional interface abstract methods that this + * class might refer to so that their unqualified. This is not perfect, as it is + * possible to have implementations of specific subtypes of parameterized + * methods. However, it will have to do for now. + * + * @param type + * @param unqualifiedMethods + * @return List of methods that should have raw unparameterized alias + */ + private List getUnqualifiedMethods(ITypeBinding type, List unqualifiedMethods) { + if (type.isArray() || type.isPrimitive()) { + return unqualifiedMethods; + } + ITypeBinding superClass = type.getSuperclass(); + if (superClass != null) + unqualifiedMethods = getUnqualifiedMethods(superClass, unqualifiedMethods); + ITypeBinding[] superInterfaces = type.getInterfaces(); + for (int i = 0; i < superInterfaces.length; i++) + unqualifiedMethods = getUnqualifiedMethods(superInterfaces[i], unqualifiedMethods); + IMethodBinding functionalMethod = type.getFunctionalInterfaceMethod(); + if (functionalMethod != null) { + if (unqualifiedMethods == null) + unqualifiedMethods = new ArrayList(); + unqualifiedMethods.add(functionalMethod); + } + return unqualifiedMethods; + } + + /** + * If there is no Foo() or Foo(xxx... array), then we need to provide our own + * constructor. + * + */ + private void addDefaultConstructor() { + if (class_haveDefaultConstructor) { + class_haveDefaultConstructor = false; + } else { + buffer.append("\nClazz.newMeth(C$);\n"); + } + } + + /** + * Add all the Enum constants, and create C$.values$() and Enum.valueOf + * + * @param constants + */ + + private void addEnumConstants(EnumDeclaration e, List enums) { + List constants = e.enumConstants(); + buffer.append("$vals=Clazz.array(C$,[0]);\n"); + for (int i = 0; i < constants.size(); i++) { + EnumConstantDeclaration enumConst = (EnumConstantDeclaration) constants.get(i); + enums.add(enumConst); + checkAnnotations(enumConst, CHECK_ANNOTATIONS_ONLY); // for JAXB only + IMethodBinding binding = enumConst.resolveConstructorBinding(); + AnonymousClassDeclaration anonDeclare = enumConst.getAnonymousClassDeclaration(); + String anonName = null; + if (anonDeclare != null) { + ITypeBinding dbinding = anonDeclare.resolveBinding(); + // BH: add the anonymous class definition inline! + addClassOrInterface(anonDeclare, dbinding, anonDeclare.bodyDeclarations(), 'a'); +// anonDeclare.accept(this); + anonName = getJavaClassNameQualified(dbinding); + buffer.append("\n"); + } + buffer.append("Clazz.newEnumConst($vals, ") + .append(getFinalMethodNameWith$Params("C$.c$", null, binding, null, false, METHOD_NOTSPECIAL)) + .append(", \""); + enumConst.getName().accept(this); + buffer.append("\", " + i); + addMethodParameterList(enumConst.arguments(), binding, ", [", "]", METHOD_CONSTRUCTOR); + if (anonName != null) + buffer.append(", ").append(anonName); + buffer.append(");\n"); + } + } + + /** + * + * Handle all field declarations without visit(FieldDeclaration). + * + * + * @param field the field being declared + * @param fields + * @param isStatic + * @return true if anything was written to the buffer + */ + @SuppressWarnings("null") + private boolean addFieldDeclaration(FieldDeclaration field, boolean isStatic) { + + List fragments = field.fragments(); + VariableDeclarationFragment identifier = (VariableDeclarationFragment) fragments.get(0); + IVariableBinding var = identifier.resolveBinding(); + Type nodeType = (var != null && var.getType().isArray() ? null : field.getType()); + boolean isPrimitive = (nodeType != null && nodeType.isPrimitiveType()); + // have to check here for final Object = "foo", as that must not be ignored. + boolean checkFinalConstant = ((isPrimitive + || var != null && var.getType().getQualifiedName().equals("java.lang.String")) && isStatic + && Modifier.isFinal(field.getModifiers())); + boolean haveAnnotations = (!isStatic && var != null && var.getAnnotations().length > 0); + + addJ2SDoc(field); + int len0 = buffer.length(); + int tpt = (isPrimitive ? fieldInfo.getPrimitiveDefaultType(((PrimitiveType) nodeType).getPrimitiveTypeCode()) : -1); + for (Iterator iter = fragments.iterator(); iter.hasNext();) { + VariableDeclarationFragment fragment = (VariableDeclarationFragment) iter.next(); + Expression initializer = fragment.getInitializer(); + IVariableBinding fbinding = fragment.resolveBinding(); + String name = getFinalFieldName(fbinding); + fieldInfo.addField(isStatic, name, var, tpt); + haveFields = true; + if (initializer != null) { + if (checkFinalConstant && getConstantValue(initializer, false)) + continue; + String prefix = (isStatic ? "C$." : "this.") + name; + buffer.append(prefix); + buffer.append("="); // no space here; will check last char for lambda + addExpressionAsTargetType(initializer, field.getType(), "v", null); + buffer.append(";\n"); + } + } + boolean wasAdded = (buffer.length() > len0); + return wasAdded && (isStatic || haveAnnotations); + } + + /** + * + * generate the Clazz.new_(...) call for an inner class. + * + * @param node + * @param javaInnerClassName + * @param outerClassExpr + * @param constructorDeclaration + * @param superAnonName the name of the superclass of the anonymous + * method, as for example, in MouseAdapter adapter + * = new MouseAdapter() {....} + */ + private void addInnerTypeInstance(ASTNode node, + ITypeBinding binding, + ITypeBinding superAnonOrInnerClass, + String javaInnerClassName, + Expression outerClassExpr, + IMethodBinding constructorMethodDeclaration, + String superAnonName, + String anonName) { + openNew(superAnonOrInnerClass == null ? binding : superAnonOrInnerClass, (superAnonName == null ? javaInnerClassName : superAnonName), anonName, + constructorMethodDeclaration, -1); + + // add constructor application arguments: [object, parameters] + + buffer.append(", ["); + + + if (outerClassExpr == null) { + buffer.append("this"); + } else { + outerClassExpr.accept(this); + } + // add final variable array + + Set list = package_htClassKeyToVisitedFinalVars.get(binding.getKey()); + String finals = listFinalVariables(list, true); + buffer.append(", ").append(finals == null ? "null" : finals); + + // add parameters + + if (constructorMethodDeclaration != null) { + List args = ((ClassInstanceCreation) node).arguments(); + addMethodParameterList(args, constructorMethodDeclaration, + args.size() > 0 || constructorMethodDeclaration.isVarargs() ? ", " : null, null, + METHOD_CONSTRUCTOR); + } + buffer.append("]"); + + // an anonymous class will be calling a constructor in another + // class, so + // we need to indicate its actual call explicitly + + if (superAnonName != null && javaInnerClassName != null) + buffer.append(",").append(getFinalJ2SClassName(javaInnerClassName, FINAL_PC)); + + buffer.append(")"); + } + + /** + * Add a method parameter list + * + * @param arguments + * @param methodDeclaration + * @param prefix + * @param suffix + * @param flags + */ + private void addMethodParameterList(List arguments, IMethodBinding methodDeclaration, String prefix, + String suffix, int flags) { + if (methodDeclaration == null) + return; + + boolean methodIsVarArgs = methodDeclaration.isVarargs(); + int argCount = arguments.size(); + if ((flags & METHOD_CONSTRUCTOR) != 0 && argCount == 0) { + // We allow use of a default constructor using new foo(). + // Here we always add a [] argument to a default constructor, as null + // will indicate that we did not use Clazz.new_ and instead called new foo() + // directly. + if (prefix != null) { + buffer.append(prefix); + prefix = null; + if (methodIsVarArgs) + buffer.append("[]"); + } + } else { + ITypeBinding[] parameterTypes = methodDeclaration.getParameterTypes(); + int nparam = parameterTypes.length; + if (prefix != null && (nparam > 0 || methodIsVarArgs)) { + buffer.append(prefix); + prefix = null; + } + addMethodArguments(parameterTypes, methodIsVarArgs, arguments, flags); + } + if (prefix == null && suffix != null) + buffer.append(suffix); + } + + private void addSuperConstructor(SuperConstructorInvocation node, IMethodBinding methodDeclaration) { + if (node == null) { + // default constructor + buffer.append("Clazz.super_(C$, this);\n"); + return; + } + buffer.append(getFinalMethodNameWith$Params(";C$.superclazz.c$", null, node.resolveConstructorBinding(), null, + false, METHOD_NOTSPECIAL)); + buffer.append(".apply(this"); + addMethodParameterList(node.arguments(), methodDeclaration, ", [", "]", METHOD_CONSTRUCTOR); + buffer.append(")"); + addCallInit(); + } + +// /** +// * Check whether the class represented by the given name is inherited from the +// * given type binding. +// * +// * The algorithm: 1. Check binding self class name 2. Check binding super class +// * 3. Check binding interfaces +// * +// * @param binding +// * @param name +// * @return +// */ +// static private boolean isInheritedClassName(ITypeBinding binding, String name) { +// if (binding == null) { +// return false; +// } +// String bindingName = removeBrackets(binding.getJavaClassNameQualified()); +// if (name.equals(bindingName)) { +// return true; +// } +// ITypeBinding superclass = binding.getSuperclass(); +// if (isInheritedClassName(superclass, name)) { +// return true; +// } +// ITypeBinding[] interfaces = binding.getInterfaces(); +// if (interfaces != null) { +// for (int i = 0; i < interfaces.length; i++) { +// if (isInheritedClassName(interfaces[i], name)) { +// return true; +// } +// } +// } +// return false; +// } +// +// /** +// * nonAnonymous classes only +// * +// * @param binding +// * @return +// */ +// private String getNamedClassName(ITypeBinding binding) { +// String innerClassName = getClassNameQualifiedAllowP$( +// binding.isLocal() ? binding.getBinaryName() : binding.getJavaClassNameQualified()); +// return innerClassName.substring(innerClassName.lastIndexOf('.') + 1); +// } + + /** + * log to sysout + * + * @param msg + */ + static void log(String msg) { + System.out.println(msg); + } + + /** + * log to syserr + * + * @param msg + */ + static void logErr(String msg) { + System.err.println(msg); + } + + private static boolean isObjectOrNull(ITypeBinding type) { + return type == null || "java.lang.Object".equals(type.getQualifiedName()); + } + + /** + * holds all static field definitions for insertion at the end of the class def + * and allows setting of local typed integer arrays for fast processing of bytes + * + */ + protected TrailingBuffer trailingBuffer = new TrailingBuffer(); + + /** + * TrailingBuffer holds definitions that need to come after all methods are + * defined, with blocks defined just once for any given class. + * + * The buffer also provides a very efficient way to do byte, short, and int + * operation processing by using the trick that if we have defined + * + * var $b$ = new Int8Array(1) + * + * then we can use that to "filter" a (necessarily) 32-bit integer JavaScript + * variable to reproduce the effect of being a byte or short or int. This is + * done as follows: + * + * Java: + * + * byte b = (byte) 300; + * + * JavaScript: + * + * var b = ($b$[0] = 300, $b$[0]); + * + * This works because Int8Arrays really are bytes, and they can only hold bytes. + * So + * + * $b$[0] = 300 + * + * sets $b$[0] to be 44, and ($b$[0] = 300, $b$[0]) then passes that value on to + * the receiving variable b (which itself is a 32-bit integer, actually). + * + * It was a surprise to me that the "(..., $b$[0])" business was necessary. + * However, it turns out that + * + * b = $b$[0] = 300; + * + * is really short for the two (undesired) independent processes: + * + * b = 300; $b$[0] = 300; + * + * not the (desired) sequential pair + * + * $b$[0] = 300; b = $b$[0]; + * + * But + * + * var b = ($b$[0] = 300, $b$[0]); + * + * is precisely this second meaning. + * + * + * We turn this action off using the field isArray so that these don't get + * nested. + * + * @author Bob Hanson + * + */ + class TrailingBuffer { + + StringBuffer buf; + private String added = ""; + + boolean hasAssert; + + TrailingBuffer() { + buf = new StringBuffer(); + } + + TrailingBuffer append(String s) { + buf.append(s); + return this; + } + + String getAssertString() { + return (hasAssert ? "C$.$_ASSERT_ENABLED_ = ClassLoader.getClassAssertionStatus$(C$);\n" : ""); + } + + public String toString() { + return getAssertString() + added + buf; + } + + void addType(String name) { + char a = name.charAt(0); + // note that this character may not be in the phrase "new Int Array" + if (added.indexOf(a) >= 0) + return; + added += "var $" + a + "$"; + switch (a) { + case 'b': // $b$ + added += " = new Int8Array(1)"; + break; + case 's': // $s$ + added += " = new Int16Array(1)"; + break; + case 'i': // $i$ // abandoned - using |0 + added += " = new Int32Array(1)"; + break; + default: + case 'p': // $p$ // char temp + case 'j': // $j$ // [pt++] temp + case 'k': // $k$ // [3][pt++] + case 'l': // $l$ // [3][4][pt++] + break; + } + added += ";\n"; + } + + public void insert(int pt, String s) { + buf.insert(pt, s); + } + } + + public boolean visit(ArrayAccess node) { + node.getArray().accept(this); + buffer.append('['); + addNonCharacter(node.getIndex()); + buffer.append(']'); + return false; + } + + @SuppressWarnings("unchecked") + public boolean visit(ArrayCreation node) { + // new byte[] {'a', 2, 3, 4, 5}; + ArrayInitializer inode = node.getInitializer(); + if (inode == null) { + addArrayConstructor(node.resolveTypeBinding(), node.dimensions()); + } else { + visit(inode); + } + return false; + } + + private void addArrayConstructor(ITypeBinding binding, List dim) { + boolean isLambda_C = (dim == null); + @SuppressWarnings("null") + int n = (isLambda_C ? 1 : dim.size()); + buffer.append(clazzArray(binding, ARRAY_DIM_ONLY)); + buffer.append(", ["); + if (isLambda_C) + buffer.append("t.intValue()"); + else + visitList(dim, ", "); + for (int i = binding.getDimensions() - n; --i >= 0;) + buffer.append(", null"); + buffer.append("])"); + } + + public boolean visit(ArrayInitializer node) { + // as in: public String[] d = {"1", "2"}; + buffer.append(clazzArray(node.resolveTypeBinding(), ARRAY_INITIALIZED)); + buffer.append(", ["); + @SuppressWarnings("unchecked") + List expressions = node.expressions(); + visitList(expressions, ", "); + buffer.append("])"); + return false; + } + + public boolean visit(Assignment node) { + // note that this is not + // var x = ..... -- that is a visit(VariableDeclaration) + // + // includes: = + // +=, -=, *=, /=, %= + // &=, |=, ^= + // <<= + // >>= + // >>>= + + Expression left = node.getLeftHandSide(); + Expression right = node.getRightHandSide(); + ITypeBinding leftTypeBinding = left.resolveTypeBinding(); + ITypeBinding rightTypeBinding = right.resolveTypeBinding(); + String rightName = (rightTypeBinding == null ? null : rightTypeBinding.getName()); + String leftName = (leftTypeBinding == null ? null : leftTypeBinding.getName()); + if (leftName == null || rightName == null) + return false; + boolean wasArray = temp_processingArrayIndex; + temp_processingArrayIndex = (left instanceof ArrayAccess); + ArrayAccess leftArray = (temp_processingArrayIndex ? (ArrayAccess) left : null); + IVariableBinding toBinding = getLeftVariableBinding(left, leftTypeBinding); + String op = node.getOperator().toString(); + String opType = (op.length() == 1 ? null : op.substring(0, op.length() - 1)); + boolean needNewStaticParenthesis = false; + boolean isParenthesized = (right instanceof ParenthesizedExpression); +// boolean haveDocRight = (getJ2sJavadoc(right, DOC_CHECK_ONLY) != null); + if (isStaticBinding(toBinding)) { + // Static def new Test_Static().y++; + needNewStaticParenthesis = (!haveDirectStaticAccess(left)) && !(node.getParent() instanceof Statement); + if (needNewStaticParenthesis) { + buffer.append("("); + } + addLeftSidePrefixName(left); + } else { + toBinding = null; + } + + if ("boolean".equals(leftName) && "boolean".equals(rightName)) { + if (("^=".equals(op))) { + opType = "!="; + } else { + // all boolean should be OK -- no automatic here + left.accept(this); + buffer.append((opType == null ? "=" : op)); + right.accept(this); + leftName = null; + } + } else if (opType == null) { + // = operator is no problem + left.accept(this); + buffer.append("="); + addExpressionAsTargetType(right, leftTypeBinding, "=", null); + leftName = null; + } + if (leftName == null) { + // we are done + if (needNewStaticParenthesis) + buffer.append(")"); + temp_processingArrayIndex = wasArray; + return false; + } + + int ptArray = (temp_processingArrayIndex ? buffer.length() : -1); + if (toBinding != null) + addFieldName(left, toBinding); + else + left.accept(this); + int ptArray2 = (temp_processingArrayIndex ? buffer.length() : -1); + if (!"char".equals(leftName)) { + if (isIntegerType(leftName) || "boolean".equals(leftName)) { + // can't just use a |= b because that ends up as 1 or 0, not true or false. + // byte|short|int|long += ... + if (!addPrimitiveTypedExpression(left, toBinding, leftName, opType, right, rightName, null, true)) + ptArray = -1; + } else { + ptArray = -1; + // not char x .... + // not boolean x.... + // could be int, byte, short, long with =, ==, or != + // could be String x = ..... + buffer.append(' '); + buffer.append(op); + buffer.append(' '); + boolean leftIsString = leftName.equals("String"); + if ("char".equals(rightName)) { + if (right instanceof CharacterLiteral) { + // ... = 'c' + if (leftIsString) { + getConstantValue(right, true); + } else { + if (isParenthesized) + buffer.append("("); + addJ2SDoc(right); + buffer.append(0 + ((CharacterLiteral) right).charValue()); + if (isParenthesized) + buffer.append(")"); + } + } else if (leftIsString) { + // String x = (char).... + right.accept(this); + } else { + // dump ( right ) and check for right being + // String.charAt(...); + int pt = buffer.length(); + buffer.append('('); + right.accept(this); + buffer.append(")"); + addCharCodeAt(right, pt); + } + } else { + // just add the right operand + addOperandWithDoc(right, leftIsString); + } + if (needNewStaticParenthesis) { + buffer.append(")"); + } + } + return fixAssignArray(leftArray, ptArray, ptArray2, wasArray); + } + + // char left op right where op is not just "=" + + // could be +=, -=, *=, /=, >>=, etc + + buffer.append(" = String.fromCharCode("); + if (left instanceof SimpleName || left instanceof QualifiedName) { + left.accept(this); + } else { + buffer.append("("); + left.accept(this); + buffer.append(")"); + } + buffer.append(CHARCODEAT0); // .charCodeAt(0) + buffer.append(opType); + buffer.append(' '); + boolean needCharCode = false; + if (right instanceof InfixExpression) { + if (getConstantValue(right, true)) { + char c = getLastCharInBuffer(); + needCharCode = (c == '\'' || c == '"'); + } else { + buffer.append("("); + appendBoxingNode(right, true); + buffer.append(")"); + } + } else if ("char".equals(rightName)) { + Object constValue = getConstant(right); + if (constValue != null && constValue instanceof Character) { + buffer.append(((Character) constValue).charValue() + 0); + } else { + boolean needParenthesis = !(right instanceof ParenthesizedExpression + || right instanceof PrefixExpression || right instanceof PostfixExpression); + if (needParenthesis) { + buffer.append("("); + addJ2SDoc(right); + } + needCharCode = appendBoxingNode(right, false); + if (needParenthesis) { + buffer.append(")"); + } + } + } else { + appendBoxingNode(right, true); + needCharCode = false; + } + if (needCharCode) + buffer.append(CHARCODEAT0); + buffer.append(')'); + return fixAssignArray(leftArray, ptArray, ptArray2, wasArray); + } + + private IVariableBinding getLeftVariableBinding(Expression left, IBinding leftTypeBinding) { + if (left instanceof Name) { + if (leftTypeBinding instanceof IVariableBinding) { + return (IVariableBinding) leftTypeBinding; + } + } else if (left instanceof FieldAccess) { + return ((FieldAccess) left).resolveFieldBinding(); + } + return null; + } + + /** + * We must fix: + * + * this.ctype[low++] = (this.ctype[low++]|(4)|0); + * + * to read: + * + * this.ctype[$j$=low++] = (this.ctype[$j$]|(4)|0); + * + * so that the index does not get operated upon twice. + * + * But what if we had this.ctype[i++][3] += .... ? -- for now, not considering + * this! Also resets wasArray. + * + * @param ptArray + * @param ptArray2 + * @param wasArray + * @return + */ + private boolean fixAssignArray(ArrayAccess leftArray, int ptArray, int ptArray2, boolean wasArray) { + if (ptArray >= 0) { + + // dyn_ltree[(Tree._length_code[lc] + LITERALS + 1) * 2]++; + + String left = buffer.substring(ptArray, ptArray2); // zzz[xxx] + int ptIndex1 = left.indexOf("["); + int ptIndex2 = left.lastIndexOf("]"); + if (ptIndex2 - ptIndex1 > 3) { + // at least as long as zzz[i++] + String right = buffer.substring(ptArray2); + buffer.setLength(ptArray); + String name = left.substring(0, ptIndex1); + if (name.indexOf("(") >= 0) { + buffer.append("($j$=" + name + ")"); + name = "$j$"; + trailingBuffer.addType("j"); + } else { + buffer.append(name); + } + String newRight = addArrayTemps(leftArray); + int ptIndex3 = right.indexOf(left); + buffer.append(right.substring(0, ptIndex3)); + buffer.append(name); + buffer.append(newRight); + buffer.append(right.substring(ptIndex3 + left.length())); + } + } + temp_processingArrayIndex = wasArray; + return false; + } + + private String addArrayTemps(ArrayAccess leftArray) { + String right = ""; + char c = 'k'; + String left = ""; + while (leftArray != null) { + Expression exp = leftArray.getIndex(); + int pt = buffer.length(); + exp.accept(this); + int len = buffer.length() - pt; + String index = buffer.substring(pt); + buffer.setLength(pt); + if (len < 3) { + left = "[" + index + "]" + left; + right = "[" + index + "]" + right; + } else { + left = "[$" + c + "$=" + index + "]" + left; + right = "[$" + c + "$]" + right; + trailingBuffer.addType("" + c++); + } + exp = leftArray.getArray(); + if (!(exp instanceof ArrayAccess)) + break; + leftArray = (ArrayAccess) exp; + } + buffer.append(left); + return right; + } + + public boolean visit(BooleanLiteral node) { + buffer.append(node.booleanValue()); + return false; + } + + public boolean visit(CastExpression node) { + Expression expression = node.getExpression(); + ITypeBinding expBinding = expression.resolveTypeBinding(); + Type typeTO = node.getType(); + String fromValue = ""; + String toValue = ""; + // assume that casting is intentional to adjust the integer type + if (expBinding != null && typeTO.isPrimitiveType()) { + String nameFROM = expBinding.getName(); + String nameTO = ((PrimitiveType) typeTO).getPrimitiveTypeCode().toString(); + if (!nameTO.equals(nameFROM)) { + addPrimitiveTypedExpression(null, null, nameTO, null, expression, nameFROM, null, false); + return false; + } + } + buffer.append(fromValue); + expression.accept(this); + buffer.append(toValue); + return false; + } + + public boolean visit(CatchClause node) { + buffer.append(" catch ("); + node.getException().accept(this); + buffer.append(") "); + node.getBody().accept(this); + return false; + } + + public boolean visit(CharacterLiteral node) { + buffer.append('\''); + addChar(node.charValue(), buffer); + buffer.append('\''); + return false; + } + + public boolean visit(ConditionalExpression node) { + // tricky part here is that the overall expression should have a target, + // not the individual ones. + ITypeBinding binding = node.resolveTypeBinding(); + Expression expThen = node.getThenExpression(); + Expression expElse = node.getElseExpression(); + node.getExpression().accept(this); + buffer.append(" ? "); + addExpressionAsTargetType(expThen, binding, "e", null); + buffer.append(" : "); + addExpressionAsTargetType(expElse, binding, "e", null); + return false; + } + + public boolean visit(FieldAccess node) { + // Field access expression AST node type. + // FieldAccess: + // Expression . Identifier + // + // + // Note that there are several kinds of expressions that resemble field + // a ccess expressions: qualified names, this expressions, and super + // field access expressions. The following guidelines help with correct + // usage: + // -An expression like "foo.this" can only be represented as a this + // expression (ThisExpression) containing a simple name. "this" is a + // keyword, and therefore invalid as an identifier. + // -An expression like "this.foo" can only be represented as a field + // access expression (FieldAccess) containing a this expression and a + // simple name. Again, this is because "this" is a keyword, and + // therefore invalid as an identifier. + // -An expression with "super" can only be represented as a super field + // access expression (SuperFieldAccess). "super" is a also keyword, and + // therefore invalid as an identifier. + // -An expression like "foo.bar" can be represented either as a + // qualified name (QualifiedName) or as a field access expression + // (FieldAccess) containing simple names. Either is acceptable, and + // there is no way to choose between them without information about what + // the names resolve to (ASTParser may return either). + // -Other expressions ending in an identifier, such as "foo().bar" can + // only be represented as field access expressions (FieldAccess). + + IVariableBinding varBinding = node.resolveFieldBinding(); + Expression expression = node.getExpression(); + if (isStaticBinding(varBinding)) { + // e.g. i += 3 + y + ++(new >>Test_Static<<().y); + buffer.append('('); + } else { + varBinding = null; + } + expression.accept(this); + + if (varBinding != null) { + buffer.append(", "); + addQualifiedNameForBinding(varBinding, false); + buffer.append(')'); + } + buffer.append("."); + node.getName().accept(this); + return false; + } + + /** + * Infix operators (typesafe enumeration). + * + *
+	 * InfixOperator:
+	 *    *	TIMES
+	 *    /  DIVIDE
+	 *    %  REMAINDER
+	 *    +  PLUS
+	 *    -  MINUS
+	 *    <<  LEFT_SHIFT
+	 *    >>  RIGHT_SHIFT_SIGNED
+	 *    >>>  RIGHT_SHIFT_UNSIGNED
+	 *    <  LESS
+	 *    >  GREATER
+	 *    <=  LESS_EQUALS
+	 *    >=  GREATER_EQUALS
+	 *    ==  EQUALS
+	 *    !=  NOT_EQUALS
+	 *    ^  XOR
+	 *    &  AND
+	 *    |  OR
+	 *    &&  CONDITIONAL_AND
+	 *    ||  CONDITIONAL_OR
+	 * 
+ */ + + public boolean visit(InfixExpression node) { + // includes + // + // * / % + - + // << >> >>> + // < > <= >= == != + // ^ & | + // && || + // ( a == b ) + // (/** j2snative xxxx ||*/ a) == b + + Expression left = node.getLeftOperand(); + Expression right = node.getRightOperand(); + List extendedOperands = node.extendedOperands(); + + if (noDocProblem(left) && noDocProblem(right) && getConstantValue(node, true)) { + return false; + } + ITypeBinding expTypeBinding = node.resolveTypeBinding(); + if (expTypeBinding == null) + return false; + String expTypeName = expTypeBinding.getName(); + boolean isToString = (expTypeName.indexOf("String") >= 0); + + String operator = node.getOperator().toString(); + boolean isBitwise = isBitwiseBinaryOperator(node); + boolean isComparison = (!isBitwise && "!==<=>=".indexOf(operator) >= 0); + ITypeBinding leftTypeBinding = left.resolveTypeBinding(); + ITypeBinding rightTypeBinding = right.resolveTypeBinding(); + if (leftTypeBinding == null || rightTypeBinding == null) + return false; + String leftName = leftTypeBinding.getName(); + String rightName = rightTypeBinding.getName(); + + boolean leftIsInt = leftTypeBinding.isPrimitive() && isIntegerType(leftName); + boolean rightIsInt = rightTypeBinding.isPrimitive() && isIntegerType(rightName); + if ("/".equals(operator) && leftIsInt && rightIsInt) { + // left and right are one of byte, short, int, or long + // division must take care of this. + addPrimitiveTypedExpression(left, null, leftName, operator, right, rightName, extendedOperands, false); + return false; + } + + boolean toBoolean = "boolean".equals(expTypeName); + + char pre = ' '; + char post = ' '; + if (isBitwise && toBoolean) { + pre = '('; + post = ')'; + buffer.append("!!("); + } + + boolean isDirect = isBitwise && !toBoolean && leftIsInt && rightIsInt; + if (isDirect || isComparison) { + + // we do not have to do a full conversion + // possibilities include + // numeric op numeric + // char/Character op char/Character + // String op String + // + if (!isDirect) + switch (leftName) { + case "char": + case "Character": + case "String": + switch (rightName) { + case "char": + case "Character": + case "String": + isDirect = true; + break; + default: + break; + } + break; + default: + if (isIntegerType(leftName) && isIntegerType(rightName)) + isDirect = true; + break; + } + + if (isDirect) { + appendBoxingNode(left, false); + buffer.append(' '); + buffer.append(operator); + buffer.append(' '); + appendBoxingNode(right, false); + addExtendedOperands(extendedOperands, operator, pre, post, isToString); + return false; + } + } + +// boolean isToStringLeft = isToString && !isBitwise; +// boolean isToStringRight = isToString && !isBitwise; + + // String s = "e"; + // s += 'c' | 'd'; + + // left + + addOperandWithDoc(left, isToString && !isBitwise); + buffer.append(' '); + // op + buffer.append(operator); + if (("==".equals(operator) || "!=".equals(operator)) && !leftTypeBinding.isPrimitive() + && !(left instanceof NullLiteral) && !(right instanceof NullLiteral)) { + buffer.append('='); + } + buffer.append(' '); + // right + if (right instanceof ParenthesizedExpression || getJ2sJavadoc(right, DOC_CHECK_ONLY) != null) { + buffer.append("("); + addJ2SDoc(right); + if (right instanceof ParenthesizedExpression) + right = ((ParenthesizedExpression) right).getExpression(); + addOperand(right, isToString && !isBitwise); + buffer.append(")"); + } else { + addOperand(right, isToString && !isBitwise); + } + + // The extended operands is the preferred way of representing deeply + // nested expressions of the form L op R op R2 op R3... where the same + // operator appears between all the operands (the most common case being + // lengthy string concatenation expressions). Using the extended + // operands keeps the trees from getting too deep; this decreases the + // risk is running out of thread stack space at runtime when traversing + // such trees. ((a + b) + c) + d would be translated to: leftOperand: a + // rightOperand: b extendedOperands: {c, d} operator: + + + addExtendedOperands(extendedOperands, operator, pre, post, isToString); + if (toBoolean) + buffer.append(post); + return false; + } + + private void addOperandWithDoc(Expression exp, boolean toString) { + if (exp instanceof ParenthesizedExpression) { + buffer.append("("); + addJ2SDoc(exp); + addOperand(((ParenthesizedExpression) exp).getExpression(), toString); + buffer.append(")"); + } else { + addOperand(exp, toString); + } + } + + public boolean visit(InstanceofExpression node) { + Type right = node.getRightOperand(); + ITypeBinding binding = right.resolveBinding(); + if (binding == null) + return false; + buffer.append("Clazz.instanceOf("); + node.getLeftOperand().accept(this); + buffer.append(", "); + if (right instanceof ArrayType) { + buffer.append(clazzArray(binding, ARRAY_CLASS_LITERAL)); + } else { + buffer.append("\"" + removeBracketsAndFixNullPackageName(getJavaClassNameQualified(binding)) + "\""); + // right.accept(this); + } + buffer.append(")"); + return false; + } + + public boolean visit(Modifier node) { + return false; + } + + public boolean visit(NumberLiteral node) { + buffer.append(node.resolveConstantExpressionValue()); + return false; + } + + public boolean visit(NullLiteral node) { + ITypeBinding binding = node.resolveTypeBinding(); + if (binding != null) + buffer.append("null"); + return true; + } + + public boolean visit(ParenthesizedExpression node) { + buffer.append("("); + addJ2SDoc(node); + node.getExpression().accept(this); + buffer.append(")"); + return false; + } + + /** + * Postfix operators (typesafe enumeration). + * + *
+	 * PostfixOperator:
+	 *    ++  INCREMENT
+	 *    --  DECREMENT
+	 * 
+ */ + public boolean visit(PostfixExpression node) { + return addPrePost(node, node.getOperand(), node.getOperator().toString(), true); + } + + /** + * Prefix operators (typesafe enumeration). + * + *
+	 * PrefixOperator:
+	 *    ++  INCREMENT
+	 *    --  DECREMENT
+	 *    +  PLUS
+	 *    -  MINUS
+	 *    ~  COMPLEMENT
+	 *    !  NOT
+	 * 
+ */ + public boolean visit(PrefixExpression node) { + Expression exp = node.getOperand(); +// NO! Don't do this! !(/** @j2sNative true||*/false does not work +// if (getConstantValue(node, true)) +// return false; + String op = node.getOperator().toString(); + + if ("~".equals(op)) { + buffer.append(op); + return true; + } + return addPrePost(node, exp, node.getOperator().toString(), false); + } + + public boolean visit(QualifiedName node) { + // page.x =... + + if (getConstantValue(node, true)) + return false; + IBinding nameBinding = node.resolveBinding(); + IVariableBinding varBinding = (nameBinding instanceof IVariableBinding ? (IVariableBinding) nameBinding : null); + Name qualifier = node.getQualifier(); + boolean skipQualifier = false; + if (isStatic(nameBinding) && varBinding != null) { + addQualifiedNameForBinding(varBinding, true); + buffer.append('.'); + skipQualifier = true; + } else if (!isStaticBinding(varBinding) || qualifier.resolveBinding() instanceof ITypeBinding) { + varBinding = null; + } + String className = null; + ASTNode parent = node.getParent(); + if (!skipQualifier && parent != null && !(parent instanceof QualifiedName)) { + while (qualifier instanceof QualifiedName) { + IBinding binding = qualifier.resolveBinding(); + if (binding != null && !(binding instanceof IVariableBinding)) { + Name xqualifier = ((QualifiedName) qualifier).getQualifier(); + if (xqualifier instanceof QualifiedName) { + IBinding xbinding = xqualifier.resolveBinding(); + if (xbinding != null && !(xbinding instanceof IVariableBinding)) { + qualifier = xqualifier; + continue; + } + } + } + break; + } + IBinding binding = qualifier.resolveBinding(); + if (binding != null && !(binding instanceof IVariableBinding)) { + ITypeBinding typeBinding = qualifier.resolveTypeBinding(); + if (typeBinding != null) { + // Compiling inner Class or enum type, like: + // RadiusData.EnumType e = RadiusData.EnumType.THREE; + className = getJavaClassNameQualified(typeBinding); + if (isStatic(nameBinding)) { + className = getFinalJ2SClassNameQualifier(null, typeBinding, className, FINAL_ESCAPECACHE); + } else { + // not possible? + dumpStack("in visit(QualifiedName) outputting raw javaClassName " + nameBinding); + } + } + } + } + + if (!skipQualifier) { + if (varBinding != null) { + if (qualifier instanceof SimpleName) { + addQualifiedNameForBinding(varBinding, false); + } else { + buffer.append('('); + if (className == null) + qualifier.accept(this); + else + buffer.append(className); + buffer.append(", "); + addQualifiedNameForBinding(varBinding, false); + buffer.append(')'); + } + } else if (className == null) { + node.getQualifier().accept(this); + } else { + buffer.append(className); + } + buffer.append('.'); + } + node.getName().accept(this); + return false; + } + + public boolean visit(SimpleName node) { + // var x = ... + // this.pages .... + if (!getConstantValue(node, true)) + buffer.append(getFinalNameSimpleQualified(node)); + return false; + } + + /** + * Return a qualifier.name or C$.name + * + * Only called by + * + * @param node + * @param mBinding + * @param flags METHOD_ISQUALIFIED | METHOD_NULLEXPRESSION | LAMBDA_METHOD + * @return + */ + private String getFinalDotQualifiedNameForMethod(SimpleName node, IMethodBinding mBinding, int flags) { + if (node == null) { + // not possible? + dumpStack("node is null in simpleNameInMethodBinding " + mBinding); + } + ITypeBinding declaringClass = mBinding.getMethodDeclaration().getDeclaringClass(); + String name = mBinding.getName(); + if (node == null) { + // lambda::method needs to be qualified here only if it is a functional + // interface method + // otherwise it will be qualified in getQualifiedSimpleNameForinvocation + name = (mBinding.getDeclaringClass().getFunctionalInterfaceMethod() == null ? name : name + "$"); + } else { + String classDot = ""; +// boolean checkNameViolation = false; + ASTNode parent; + if (isStatic(mBinding)) { + if ((flags & LAMBDA_METHOD) == 0 && declaringClass != null) { + if ((flags & METHOD_NULLEXPRESSION) != 0) { + // could be C$. or P$. + String cname = removeBracketsAndFixNullPackageName(getJavaClassNameQualified(declaringClass)); + if (cname.length() > 0) + classDot = cname + "."; + } + } + } else if ((parent = node.getParent()) != null && !(parent instanceof FieldAccess)) { + if ((flags & METHOD_ISQUALIFIED) == 0 && declaringClass != null && class_shortName != null) { +// checkNameViolation = true; + classDot = getClassNameAndDot(parent, declaringClass, isPrivate(mBinding)); + } + } + name = classDot + name; + + if (classDot.length() > 0) + name = getFinalJ2SClassName(name, FINAL_PC); + +// + (checkNameViolation ? NameMapper.getJ2S$JavaScriptCollisionIdentifier(name, true, mBinding) + // : name); + } + + return name; + } + + public boolean visit(SimpleType node) { + ITypeBinding binding = node.resolveBinding(); + buffer.append(binding == null ? node : getFinalJ2SClassName(getJavaClassNameQualified(binding), FINAL_PC)); + return false; + } + + public boolean visit(StringLiteral node) { + String s = node.getEscapedValue(); + if (s.indexOf('\\') < 0) { + buffer.append(s); + } else { + // \1 doesn't work for JavaScript strict mode + String v = htStrLitCache.get(s); + if (v == null) { + htStrLitCache.put(s, v = !po0.matcher(s).find() ? s : replaceOctal(s)); + } + buffer.append(v); + } + return false; + } + + // \n, \nn, \nnn octal check -- ok for general JavaScript, + // but ECMAScript 6 does not accept this, and "use strict" does not, either. + private Pattern po0=Pattern.compile("([\\\\])([0-7])"); + private Pattern po00=Pattern.compile("([\\\\])([0-7][0-7])"); + private Pattern po000=Pattern.compile("([\\\\])([0-7][0-7][0-7])"); + + private String replaceOctal(String s) { + return po0.matcher(po00.matcher(po000.matcher(s).replaceAll("\\\\u0$2")).replaceAll("\\\\u00$2")).replaceAll("\\\\u000$2"); + } + + /** + * SuperFieldAccess: + * + * [ ClassName . ] super . Identifier + * + */ + public boolean visit(SuperFieldAccess node) { + buffer.append("this."); + buffer.append(getFinalFieldName((IVariableBinding) node.getName().resolveBinding())); + return false; + } + + /** + * this or ClassName.this - just getting the qualifier here, not the whole + * thing? + * + * + */ + public boolean visit(ThisExpression node) { + if (node.getQualifier() == null && class_localType != LAMBDA_EXPRESSION) { + // not x -> this.what() + buffer.append("this"); + return false; + } + // xxxx.this.x + // xxxx.this.foo() + buffer.append(getThisRefOrSyntheticReference(node, node.resolveTypeBinding(), "this")); + return false; + } + + private String getThisRefOrSyntheticReference(ASTNode node, ITypeBinding binding, String ref) { + ASTNode classNode = (node == null ? null : getAbstractOrAnonymousParentForNode(node)); + if (class_isAnonymousOrLocal || classNode != null && classNode.getParent() != null // CompilationUnit + && classNode.getParent().getParent() != null) { + // not the top level, but "this" refers to this class + if (!binding.getBinaryName().equals(class_typeBinding.getBinaryName())) { + // not the top level -- add the synthetic reference. + // anonymous and local will not have fully qualified names + ref = getSyntheticReference(getJavaClassNameQualified(binding)); + } + } + return ref; + } + + public boolean visit(TypeLiteral node) { + // Class x = Foo.class + Type type = node.getType(); + ITypeBinding binding = type.resolveBinding(); + if (type.isPrimitiveType()) { + // adds Integer.TYPE, Float.TYPE, etc. + buffer.append(NameMapper.getPrimitiveTYPE(binding.getName()) + ".TYPE"); + } else if (type instanceof ArrayType) { + buffer.append(clazzArray(binding, ARRAY_CLASS_LITERAL)); + } else { + // BH we are creating a new Class object around this class + // if it is an interface, then we explicitly add .$methodList$ + buffer.append("Clazz.getClass("); + buffer.append(getFinalJ2SClassNameQualifier(null, binding, getJavaClassNameQualified(binding), + FINAL_ESCAPECACHE)); + if (binding.isInterface()) + addInterfaceMethodListForLiteral(binding); + buffer.append(")"); + } + return false; + } + + /** + * Add the list of methods in the Clazz.getClass call so that we can use them + * for reflection. + * + * @param binding + */ + private void addInterfaceMethodListForLiteral(ITypeBinding binding) { + buffer.append(",["); + boolean isAnnotation = binding.isAnnotation(); + IMethodBinding[] methods = binding.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + if (i > 0) + buffer.append(","); + String name = methods[i].getName(); + buffer.append("'") + .append(isAnnotation ? name : getFinalMethodNameWith$Params(null, null, methods[i], null, false, METHOD_LITERAL)) + .append("'"); + } + buffer.append("]"); + } + + @SuppressWarnings("unchecked") + public boolean visit(VariableDeclarationExpression node) { + buffer.append("var "); + visitList(node.fragments(), ", "); + return false; + } + + public boolean visit(VariableDeclarationFragment node) { + + SimpleName name = node.getName(); + IBinding binding = name.resolveBinding(); + if (binding == null) + return false; + acceptPossiblyFinalVar(name); // was 0 + Expression right = node.getInitializer(); + ITypeBinding rightBinding = (right == null ? null : right.resolveTypeBinding()); + if (rightBinding == null) + return false; + buffer.append("=");// no space here -- need to check for last char "=" in lambda + + addExpressionAsTargetType(right, name.resolveTypeBinding(), "v", null); + return false; + } + + ////////// END visit/endVisit /////////// + + /** + * pre: ++,--, +, -, ~, ! + * + * post: ++,-- + * + * @param node + * @param left + * @param op + * @param isPost + * @return + */ + @SuppressWarnings({ "null" }) + private boolean addPrePost(Expression node, Expression left, String op, boolean isPost) { + + ASTNode parent = node.getParent(); + + ITypeBinding leftTypeBinding = left.resolveTypeBinding(); + IVariableBinding varBinding = getLeftVariableBinding(left, leftTypeBinding); + String name = null; + boolean isChar = (leftTypeBinding != null && leftTypeBinding.isPrimitive() + && "char".equals(name = leftTypeBinding.getName())); + boolean isShortOrByte = ("short".equals(name) || "byte".equals(name)); + String term = ""; + if (isStaticBinding(varBinding)) { + // new Test_Static().y++ where y is static + if ((isChar || !haveDirectStaticAccess(left)) + && !(parent instanceof Statement || parent instanceof ParenthesizedExpression)) { + + buffer.append("("); + term = ")"; + } + addLeftSidePrefixName(left); + } else { + varBinding = null; + } + + if (isPost) { + if (isChar || isShortOrByte) { + addPrePostFix(left, parent, varBinding, op, false, name.charAt(0)); + } else { + // TODO: have to consider short and byte + addFieldName(left, varBinding); + buffer.append(op); + } + } else { + if (isChar || isShortOrByte) { + if (varBinding == null) + buffer.append("("); + addPrePostFix(left, (varBinding == null ? parent : null), varBinding, op, true, name.charAt(0)); + if (varBinding == null) + buffer.append(")"); + } else { + // have to consider short and byte + buffer.append(op); + addFieldName(left, varBinding); + } + + } + buffer.append(term); + return false; + } + + /** + * + * @param left + * @param parent null for prefix + * @param varBinding + * @param op + * @param b + */ + private void addPrePostFix(Expression left, ASTNode parent, IVariableBinding varBinding, String op, + boolean isPrefix, char type) { + + boolean isChar = (type == 'c'); + if (isChar) + type = 'p'; + boolean isStatement = (parent instanceof Statement && !(parent instanceof ReturnStatement)); + boolean addAnonymousWrapper = (!isChar || !isPrefix && !isStatement); + String key = "$" + type + (isChar ? "$" : "$[0]"); + if (addAnonymousWrapper) { + buffer.append("(" + key + "="); + trailingBuffer.addType("" + type); + } + if (isChar) { + if (addAnonymousWrapper) { + addFieldName(left, varBinding); + buffer.append(","); + } + addFieldName(left, varBinding); + buffer.append(isChar ? "=String.fromCharCode(" : "="); + addFieldName(left, varBinding); + buffer.append(CHARCODEAT0).append("++".equals(op) ? "+1" : "-1"); + buffer.append(")"); + } else { + // byte or short + + // Key in all cases is to return $b$[0], not just $b$[0]++ or ++$b$[0], + // as that first converts to an integer, and then applies ++ in JavaScript. + // In Java, ++ first operates on the byte, then that result is converted to + // an integer. + + boolean wasArray = temp_processingArrayIndex; + temp_processingArrayIndex = (left instanceof ArrayAccess); + if (isPrefix) + buffer.append(op); + int ptArray = (temp_processingArrayIndex ? buffer.length() : -1); + addFieldName(left, varBinding); + int ptArray2 = (temp_processingArrayIndex ? buffer.length() : -1); + buffer.append(","); + addFieldName(left, varBinding); + buffer.append("="); + if (temp_processingArrayIndex) + fixAssignArray((ArrayAccess) left, ptArray, ptArray2, wasArray); + if (isPrefix) { + // i = ($b$[0]=++b,b=$b$[0]); + // ($b$[0]=++b,b=$b$[0]); + buffer.append(key); + } else { + // postfix + // statement: ($b$[0]=b,b=(++$b$[0] ,$b$[0])) + // not statement: i = ($b$[0]=b,b=(++$b$[0] ,$b$[0]),--$b$[0],$b$[0]); + buffer.append("("); + buffer.append(op); + buffer.append(key); + if (isStatement) { + key += ")"; + } else { + buffer.append(","); + buffer.append(key); + buffer.append("),"); + buffer.append(op.equals("++") ? "--" : "++"); + buffer.append(key); + } + } + } + if (addAnonymousWrapper) { + buffer.append("," + key + ")"); + } + } + + private static boolean isBoxTyped(Expression exp) { + return exp.resolveBoxing() || exp.resolveUnboxing(); + } + + private static boolean isIntegerType(String type) { + return (type != null && type.length() > 1 && "int long byte short".indexOf(type) >= 0); + } + + static boolean isPrivate(IBinding b) { + return b != null && Modifier.isPrivate(b.getModifiers()); + } + + static boolean isStatic(IBinding b) { + return b != null && Modifier.isStatic(b.getModifiers()); + } + + static boolean isTransient(IBinding b) { + return b != null && Modifier.isTransient(b.getModifiers()); + } + + static boolean isStatic(BodyDeclaration b) { + return Modifier.isStatic(b.getModifiers()); + } + + /** + * new int[5] becomes Clazz.array(Integer.TYPE, [5]) + */ + private final static int ARRAY_DIM_ONLY = 0; + /** + * new byte[] {'a', 2, 3, 4, 5} becomes Clazz.array(Byte.TYPE, -1, ["a", 2, 3, + * 4, 5]); + */ + private final static int ARRAY_INITIALIZED = -1; + + /** + * int[][].class becomes Clazz.array(Integer.TYPE, -2) + */ + private final static int ARRAY_CLASS_LITERAL = 1; + + /** + * + * @param type + * @param dimFlag + * @return JavaScript for array creation + */ + private String clazzArray(ITypeBinding type, int dimFlag) { + ITypeBinding ebinding = type.getElementType(); + if (ebinding == null) + ebinding = type; // creating for Enum + String params = (ebinding.isPrimitive() ? NameMapper.getPrimitiveTYPE(ebinding.getName()) + ".TYPE" + : getFinalJ2SClassNameQualifier(null, ebinding, null, FINAL_ESCAPECACHE)) + + (dimFlag == ARRAY_DIM_ONLY ? "" : ", " + (Math.abs(dimFlag) * type.getDimensions() * -1)); + return "Clazz.array(" + params + (dimFlag > 0 ? ")" : ""); + } + + //////////////////////////////// + + /** + * check to change charAt to charCodeAt + * + * @param right + * @param pt + */ + private void addCharCodeAt(Expression right, int pt) { + String charCodeAt0 = CHARCODEAT0; + if (right instanceof MethodInvocation) { + // if possible, just replace "charAt" with "charCodeAt" + MethodInvocation m = (MethodInvocation) right; + if (m.resolveMethodBinding().getKey().equals("Ljava/lang/String;.charAt(I)C")) { + if ((pt = buffer.indexOf(".charAt", pt)) >= 0) { + charCodeAt0 = "Code" + buffer.substring(pt + 5); + buffer.setLength(pt + 5); + } + } + } + buffer.append(charCodeAt0); + } + + /** + * Append an expression, coercing to primitive numeric types of the target + * parameter if needed. Used for Method arguments and return values, as well as + * variable declaration fragments, where we know the target type and no operator + * is involved. + * + * + * @param exp + * @param targetType ITypeBinding or TYPE or string + * @param op just an identifier of the context: = for assignment, r for + * return statement, v for variable declaration fragment, p + * for method parameter, q for first parameter of indexOf or + * lastIndexOf, which are officially ints + */ + private void addExpressionAsTargetType(Expression exp, Object targetType, String op, List extendedOperands) { + if (targetType == null + || exp instanceof CastExpression && ((CastExpression) exp).getExpression() instanceof NullLiteral) { + addJ2SDoc(exp); + buffer.append("null"); + return; + } + ITypeBinding expTypeBinding = exp.resolveTypeBinding(); + if (expTypeBinding == null) + return; + // BH: Question: When does typeBinding == null? + // A: when there is a compilation error, I think. + // OK, now we have the same situation as any operand. + String rightName = expTypeBinding.getName(); + if (rightName.equals("char") && op == "q") { + appendBoxingNode(exp, false); + return; + } + String paramName = (exp.resolveTypeBinding().isArray() ? ";" + : targetType instanceof ITypeBinding ? ((ITypeBinding) targetType).getName() : targetType.toString()); + boolean isNumeric = isIntegerType(paramName); + if ((isNumeric || paramName.equals("char")) && !isBoxTyped(exp)) { + // using operator "m" to limit int application of $i$ + + addPrimitiveTypedExpression(null, null, paramName, op, exp, rightName, extendedOperands, false); + } else { + // char f() { return Character } + // Character f() { return char } + // int f() { return Character } + appendBoxingNode(exp, isNumeric); + } + } + + private void addExtendedOperands(List extendedOperands, String operator, char pre, char post, + boolean isToString) { + if (extendedOperands.size() > 0) { + buffer.append(' '); + for (Iterator iter = extendedOperands.iterator(); iter.hasNext();) { + buffer.append(operator); + buffer.append(pre); + ASTNode element = (ASTNode) iter.next(); + addOperand((Expression) element, isToString); + buffer.append(post); + } + } + } + + private void addFieldName(Expression left, IVariableBinding qualifier) { + if (qualifier != null) { + addQualifiedNameForBinding(qualifier, false); + buffer.append('.'); + left = (left instanceof QualifiedName ? ((QualifiedName) left).getName() + : left instanceof FieldAccess ? ((FieldAccess) left).getName() : left); + } + appendBoxingNode(left, false); + } + + /** + * add a reference to the static field prior to defining it. + * + * @param left + */ + private void addLeftSidePrefixName(Expression left) { + if (left instanceof QualifiedName) { + if ((left = ((QualifiedName) left).getQualifier()) instanceof SimpleName) + return; + } else if (left instanceof FieldAccess) { + if ((left = ((FieldAccess) left).getExpression()) instanceof ThisExpression) + return; + } else { + return; + } + left.accept(this); + buffer.append(", "); + } + + /** + * + * @param parameterTypes + * @param methodIsVarArgs + * @param arguments + * @param flags METHOD_INDEXOF | 0 + */ + @SuppressWarnings("null") + private void addMethodArguments(ITypeBinding[] parameterTypes, boolean methodIsVarArgs, List arguments, + int flags) { + String post = ", "; + int nparam = parameterTypes.length; + int argCount = arguments.size(); + boolean isIndexOf = ((flags & METHOD_INDEXOF) != 0); + for (int i = 0; i < nparam; i++) { + ITypeBinding paramType = parameterTypes[i]; + Expression arg = (i < argCount ? (Expression) arguments.get(i) : null); + String op = (isIndexOf && i == 0 ? "q" : "p"); + if (i == nparam - 1) { + // BH: can't just check for an array; it has to be an array with + // the right number of dimensions + if (nparam != argCount || methodIsVarArgs && paramType.isArray() + && arg.resolveTypeBinding().getDimensions() != paramType.getDimensions() + && !(arg instanceof NullLiteral)) { + buffer.append("["); + for (int j = i; j < argCount; j++) { + addExpressionAsTargetType((Expression) arguments.get(j), paramType, op, null); + if (j != argCount - 1) { + buffer.append(", "); + } + } + buffer.append("]"); + break; + } + post = ""; + } + addExpressionAsTargetType(arg, paramType, op, null); + buffer.append(post); + } + } + + /** + * Do not allow char or Character in a switch or array; instead use int + * + * @param exp + */ + private void addNonCharacter(Expression exp) { + String name = exp.resolveTypeBinding().getName(); + switch (name) { + case "char": + case "Character": + addOperand(exp, false); + break; + case "Byte": + case "Short": + case "Integer": + case "Long": + addOperand(exp, false); + break; + default: + case "String": + exp.accept(this); + break; + } + } + + /** + * add the operand, checking to see if it needs some adjustment: + * + * (a) String + x where x is {double/float} requires boxing + * Double/Float(x).toString() + * + * (b) String + x where x is {Double/Float} requires added .toString() + * + * (c) Character and char to numeric requires addition of .$c() + * + * + * + * @param exp + * @param isToString + */ + private void addOperand(Expression exp, boolean isToString) { + + boolean needRtParen = false;// (exp instanceof ParenthesizedExpression && getJ2sJavadoc(exp, DOC_CHECK_ONLY) + // != null); + ITypeBinding binding = exp.resolveTypeBinding(); + String name = binding.getName(); + if (isToString) { + String prefix = null, suffix = null; + switch (name) { + case "double": + prefix = "new Double("; + suffix = ")"; + break; + case "float": + prefix = "new Float("; + suffix = ")"; + break; + case "Double": + case "Float": + prefix = suffix = ""; + break; + default: + exp.accept(this); + break; + } + if (prefix != null) { + buffer.append(prefix); + exp.accept(this); + buffer.append(suffix); + buffer.append(".toString()"); + } + } else if (!binding.isPrimitive() || !"char".equals(name)) { + appendBoxingNode(exp, !isToString); + } else { + // to numeric only + // oddly enough, 'c' is considered a simple + Object constValue = null; + if (exp instanceof CharacterLiteral) { + buffer.append(0 + ((CharacterLiteral) exp).charValue()); + } else if ((constValue = getConstant(exp)) != null && constValue instanceof Character) { + buffer.append(0 + ((Character) constValue).charValue()); + } else if (exp instanceof SimpleName || exp instanceof QualifiedName) { + int pt = buffer.length(); + appendBoxingNode(exp, false); + if (pt == buffer.length() - 3 && buffer.charAt(pt) == '\'') { + char c = buffer.charAt(pt + 1); + buffer.setLength(pt); + buffer.append((int) c); + } else { + buffer.append(CHARCODEAT0); + } + } else if (exp instanceof PrefixExpression || exp instanceof PostfixExpression + || exp instanceof ParenthesizedExpression) { + appendBoxingNode(exp, false); + buffer.append(CHARCODEAT0); + } else { + int pt = buffer.length(); + buffer.append("("); + appendBoxingNode(exp, false); + buffer.append(")"); + addCharCodeAt(exp, pt); + } + } + if (needRtParen) + buffer.append(")"); + } + /** + * A general method to handle implicit casting. + * + * @param left + * @param assignmentBinding + * @param leftName + * @param op + * @param right + * @param rightName + * @param extendedOperands + * @param isAssignment (+=, &=, etc) + * @param return true if is an assignment and a = (a op b) was + * used + */ + private boolean addPrimitiveTypedExpression(Expression left, IVariableBinding assignmentBinding, String leftName, + String op, Expression right, String rightName, List extendedOperands, boolean isAssignment) { + // byte|short|int|long /= ... + // convert to proper number of bits + + // byte a |= right + + // becomes + + // a = ($b$[0] = a | right, $b$[0]) + + String classIntArray = null; + String more = null; + + String prefix = (isAssignment ? "=" : ""); + boolean fromChar = ("char".equals(rightName)); + boolean fromIntType = ("long int short byte".indexOf(rightName) >= 0); + boolean addParens = (op != "r" || fromChar || right instanceof ParenthesizedExpression); + boolean isDiv = "/".equals(op); + boolean toChar = false; + switch (leftName) { + case "char": + if (!fromChar) { + prefix += "String.fromCharCode("; + more = ")"; + addParens = false; + } + toChar = true; + break; + default: + // double, float + break; + case "long": + if (!fromIntType || isDiv) { + more = "|0)"; + addParens = true; + } else { + left = null; + } + break; + case "int": + if (!isDiv && (op != null && (!isDiv && fromIntType) || fromChar || rightName.equals("short") + || rightName.equals("byte"))) { + left = null; + } else { + more = "|0)"; + addParens = true; + } + break; + case "short": + if ((rightName.equals("short") || rightName.equals("byte")) && !isDiv) { + left = null; + break; + } + //$FALL-THROUGH$ + case "byte": + if (temp_processingArrayIndex) { + more = "|0)"; + addParens = true; + } else { + classIntArray = "$" + leftName.charAt(0) + "$[0]"; // $i$, etc. + trailingBuffer.addType(leftName); + } + break; + } + boolean wasArray = temp_processingArrayIndex; + + if (isAssignment && left == null) { + buffer.append(op); + } +// buffer.append("OP_" + op + " " + isAssignment + " left=" + left + "PREFIX_" + prefix + "_PREFIX"); + buffer.append(prefix); + if (classIntArray != null) { + if (addParens) + buffer.append("("); + buffer.append(classIntArray).append(" = "); + temp_processingArrayIndex = true; + } else if (more == "|0)") { + buffer.append("("); + } + if (left != null) { + // a += b + addFieldName(left, assignmentBinding); + buffer.append(op); + if (isAssignment) + buffer.append("("); + } + if (!appendBoxingNode(right, fromChar) && fromChar && !toChar) { + buffer.append(CHARCODEAT0); + } + if (extendedOperands != null) { + addExtendedOperands(extendedOperands, op, ' ', ' ', false); + } + if (left != null && isAssignment) { + buffer.append(")"); + } + if (classIntArray != null) { + // this is necessary because in JavaScript, (a=3.5) will be 3.5, not + // a: + // a = new Int8Array(1) + // (a[0]=3.4, a[0]) + // 3 + // (a[0]=3.4) + // 3.4 + buffer.append(", ").append(classIntArray); + if (addParens) + buffer.append(")"); + temp_processingArrayIndex = wasArray; + } else if (more != null) { + buffer.append(more); + } + return (isAssignment && left != null); + } + + /** + * for example: new Test_Static().y++ + * + * @param varBinding + * @param isStatic + */ + private void addQualifiedNameForBinding(IVariableBinding varBinding, boolean isStatic) { + ITypeBinding declaringClass = varBinding.getDeclaringClass(); + String javaClassName = getJavaClassNameQualified(declaringClass); + isStatic = isStatic(varBinding); + String finalQualifiedName = getFinalJ2SClassName(javaClassName, FINAL_PC); + if (isStatic) {// && (isStatic || finalQualifiedName.length() < 2 || + // finalQualifiedName.charAt(1) != '$')) { + finalQualifiedName = getFinalJ2SClassNameQualifier(null, declaringClass, javaClassName, FINAL_ESCAPECACHE); + } + buffer.append(finalQualifiedName); + } + + /** + * Determine the qualifier for a method or variable. + * + * In the case of private nonstatic methods, this is "p$". + * + * For general fields, this will be "this.". + * + * For fields in outer classes, we need a synthetic references, + * this.b$[className] that points to the outer object, which may be one or more + * levels higher than this one. + * + * Anonymous inner classes may reference either a superclass method/field or one + * in its declaring class stack. + * + * @param node either a method or field or local variable + * @param declaringClass the class that declares this variable + * @param isPrivateAndNotStatic + * @return qualifier for method or variable + */ + private String getClassNameAndDot(ASTNode node, ITypeBinding declaringClass, boolean isPrivateAndNotStatic) { + + String name = getJavaClassNameQualified(declaringClass); + String ret = ""; + int superLevel = 0; + boolean isThis = false; + + // Search parents of this node for an anonymous or abstract class declaration + while (node != null) { + boolean isAnonymous = (node instanceof AnonymousClassDeclaration); + ITypeBinding typeBinding = (isAnonymous ? ((AnonymousClassDeclaration) node).resolveBinding() + : node instanceof AbstractTypeDeclaration ? ((AbstractTypeDeclaration) node).resolveBinding() + : null); + if (typeBinding != null) { + // is anonymous or abstract class declaration + superLevel++; + if (isSuperType(declaringClass, typeBinding)) { + if (isPrivateAndNotStatic) { + ret = getPrivateVar(declaringClass, false) + "."; + isThis = true; + } else if (superLevel == 1) { + ret = "this."; + isThis = (class_localType != LAMBDA_EXPRESSION); + } + name = getJavaClassNameQualified(typeBinding); + break; + } + } + node = node.getParent(); + } + return (isThis ? ret : getSyntheticReference(name) + "."); + } + +// private String ensureNameIfLocal(String name, ITypeBinding typeBinding, ASTNode parent) { +// if ((name == null || name.length() == 0) && typeBinding.isLocal()) { +// name = typeBinding.getBinaryName(); +// int idx0 = name.lastIndexOf("."); +// if (idx0 == -1) { +// idx0 = 0; +// } +// int idx1 = name.indexOf('$', idx0); +// if (idx1 != -1) { +// int idx2 = name.indexOf('$', idx1 + 1); +// String parentAnon = ""; +// if (idx2 == -1) { // maybe the name is already +// // "$1$2..." for Java5.0+ in +// // Eclipse 3.2+ +// parent = parent.getParent(); +// while (parent != null) { +// if (parent instanceof AbstractTypeDeclaration) { +// break; +// } else if (parent instanceof AnonymousClassDeclaration) { +// AnonymousClassDeclaration atype = (AnonymousClassDeclaration) parent; +// ITypeBinding aTypeBinding = atype.resolveBinding(); +// String aName = aTypeBinding.getBinaryName(); +// parentAnon = aName.substring(aName.indexOf('$')) + parentAnon; +// } +// parent = parent.getParent(); +// } +// name = name.substring(0, idx1) + parentAnon + name.substring(idx1); +// } +// } +// } +// return name; +// } + + /** + * Get the synthetic reference for inner classes that reference "this" for outer + * classes + * + * + * @param className + * @return "this" + .qualifier + */ + private String getSyntheticReference(String className) { + return "this" + (className.equals("java.lang.Object") || className.equals("Object") ? "" + : className.equals(this$0Name) ? ".this$0" + : ".b$['" + getFinalJ2SClassName(className, FINAL_RAW) + "']"); + } + + /** + * box or unbox as necessary + * + * @param element + * @param toCharCode true to append .c$(), not .valueOf(); + * @return true if boxing or unboxing + */ + private boolean appendBoxingNode(ASTNode element, boolean toCharCode) { + // Double > x will be unboxed + // Character == 'c' will be unboxed + // f$Integer(int) will be boxed + if (element instanceof Expression) { + Expression exp = (Expression) element; + if (exp.resolveBoxing()) { + // expression is the site of a boxing conversion + ITypeBinding typeBinding = exp.resolveTypeBinding(); + if (typeBinding.isPrimitive()) { + String name = typeBinding.getName(); + name = (name.equals("char") ? "Character" + : name.equals("int") ? "Integer" + : Character.toUpperCase(name.charAt(0)) + name.substring(1)); + buffer.append("new " + name + "("); + element.accept(this); + buffer.append(")"); + return true; + } + } else if (exp.resolveUnboxing()) { + // expression is the site of an unboxing conversion + ITypeBinding typeBinding = exp.resolveTypeBinding(); + if (!typeBinding.isPrimitive()) { + buffer.append("("); + element.accept(this); + if (toCharCode) { + String name = getJavaClassNameQualified(typeBinding); + if (toCharCode && name.length() == 0) + name = typeBinding.getKey(); + if (name.indexOf("Character") >= 0) { + buffer.append(").intValue$()"); + return true; + } + } + buffer.append(").valueOf()"); + return true; + } + } + if (!(exp instanceof ParenthesizedExpression) && !(exp instanceof PrefixExpression) + && !(exp instanceof InfixExpression) && !(exp instanceof PostfixExpression) + && getConstantValue(exp, true)) { + return false; + } + } + element.accept(this); + return false; + } + + private boolean checkSimpleBooleanOperator(String op) { + return (op.equals("^") || op.equals("|") || op.equals("&")); + } + + /** + * Check to see if we have a static variable. + * + * @param varBinding + * @return + */ + private boolean isStaticBinding(IVariableBinding varBinding) { + return (isStatic(varBinding) && varBinding.getDeclaringClass() != null); + } + + static String getJavaClassNameQualified(ITypeBinding binding) { + // about binding.isLocal() + // + // Returns whether this type binding represents a local class. + // A local class is any nested class or enum type not declared as a member of + // another class or interface. A local class is a subspecies of nested type, and + // mutually exclusive with member types. For anonymous classes, which are + // considered a subspecies of local classes, this method returns true. + // Note: This deviates from JLS3 14.3, which states that anonymous types are not + // local types since they do not have a name. Also note that interfaces and + // annotation types cannot be local. + // + // about binding.getBinaryName() + // + // Note that in some cases, the binary name may be unavailable. This may happen, + // for example, for a local type declared in unreachable code. + // + // about binding.getKey() (not relevant) + // + // Note that the key for member value pair bindings is not yet implemented. This + // method returns null for that kind of bindings. + // + + if (binding == null) { + return null; + } + if (binding.isTypeVariable()) + return binding.toString(); + String name = null, bindingKey; + if ((binding.isAnonymous() || binding.isLocal()) && (name = binding.getBinaryName()) == null + && (bindingKey = binding.getKey()) != null) + name = bindingKey.substring(1, bindingKey.length() - 1).replace('/', '.'); + if (name == null) { + name = binding.getQualifiedName(); + if (name.length() == 0) { + name = binding.getBinaryName(); + if (name == null) { + System.out.println(">>name null?? bn=" + binding.getBinaryName() + " qn=" + binding.getQualifiedName() + " n=" + binding.getName() + " k=" +binding.getKey() + + " isAnon" + binding.isAnonymous() + " " + binding.isLocal()); + + name = ""; // + } + } + } + return name; + } + + private static String getJavaClassNameSuperNoBrackets(ITypeBinding typeBinding) { + ITypeBinding superclass = typeBinding.getSuperclass(); + if (superclass == null) + return null; + String qualifiedName = removeBracketsAndFixNullPackageName(getJavaClassNameQualified(superclass)); + return ("java.lang.Object".equals(qualifiedName) || "java.lang.Enum".equals(qualifiedName) ? null + : qualifiedName); + } + + private ITypeBinding getJavaClassSuper(ITypeBinding typeBinding) { + return (typeBinding == null ? null : typeBinding.getSuperclass()); + } + + private int lambdaCount = 0; + + private String getMyJavaClassNameLambda(boolean andIncrement) { + return package_name + "." + class_shortName.replace('.', '$') + "$lambda" + + (andIncrement ? ++lambdaCount : lambdaCount); + } + + static String stripJavaLang(String name) { + // shorten java.lang.XXX.YYY but not java.lang.xxx.YYY + String s = (!name.startsWith("java.lang.") || name.equals("java.lang.Object") + || name.length() > 10 && !Character.isUpperCase(name.charAt(10)) ? name : name.substring(10)); + return s; + } + + /** + * From visit(SimpleName) only. + * + * @param node + * @return + */ + private String getFinalNameSimpleQualified(SimpleName node) { + // xxx.yyy.zzz... + IBinding binding = node.resolveBinding(); + ASTNode parent = node.getParent(); + if (parent == null) { + // this does not appear to be reachable + dumpStack("parent is null in returnFinalNameSimpleQualified"); + return node.toString(); + } + char leadingChar = getLastCharInBuffer(); + boolean isQualified = (leadingChar == '.'); + // looking for "." or '"' here. + if (isQualified && parent instanceof QualifiedName) { + if (binding instanceof IVariableBinding) + return getFinalFieldName((IVariableBinding) binding); + // TODO could be a problem here is we have a conversion of a class in .j2s. + // package or class-qualified: + // java.lang.Math.xxx "lang" or "Math" + return node.toString(); + } + if (parent instanceof ClassInstanceCreation && !(binding instanceof IVariableBinding)) { + if (binding == null) { + // not possible? + dumpStack(" binding null in ClassInstanceCreation"); + } + return getFinalJ2SClassName(getJavaClassNameQualified(node.resolveTypeBinding()), FINAL_PC); + } + if (binding instanceof IVariableBinding) + return getFinalNameForNode(node, leadingChar, (IVariableBinding) binding); + + if (binding instanceof IMethodBinding) { +// Arises when a lambda expression is used as a method parameter, +// in the one case we have, as a parameter to a constructor in java.util.stream.Collectors: +// In this case, "add" is the name: +// +// return new CollectorImpl<>(collectionFactory, +// Collection::add, +// (r1, r2) -> { r1.addAll(r2); return r1; }, +// CH_ID); +// } +// ... +// CollectorImpl( +// Supplier supplier, +// BiConsumer accumulator, +// BinaryOperator combiner, +// Set characteristics +// ) { +// this(supplier, accumulator, combiner, castingIdentity(), characteristics); +// } + return getFinalDotQualifiedNameForMethod(node, (IMethodBinding) binding, + (isQualified ? METHOD_ISQUALIFIED : METHOD_NOTSPECIAL)); + } + + // a class reference + + // static reference such as explicit "java.lang.Double" in Point2D + ITypeBinding typeBinding = node.resolveTypeBinding(); + if (typeBinding == null) + return node.getFullyQualifiedName(); + String name = getFinalJ2SClassName(getJavaClassNameQualified(typeBinding), FINAL_PC); + return NameMapper.getJavaScriptCollisionIdentifier(name, true); + } + + /** + * Get the final JavaScript name for a local variable, static or nonstatic + * field, which may or may not be a method qualifier. + * + * Local variables are possibly prefixed with "this.$finals$." + * + * @param node + * @param lastBufferChar looking for period, single quote, or double quote + * @param varBinding + * @return a fully qualified variable name + */ + private String getFinalNameForNode(SimpleName node, char lastBufferChar, IVariableBinding varBinding) { + String qualifier = ""; + IVariableBinding variableDeclaration = varBinding.getVariableDeclaration(); + ITypeBinding declaringClass = variableDeclaration.getDeclaringClass(); + String name = varBinding.getName(); + String j2sName = getFinalFieldOrLocalVariableName(declaringClass, name); + if (isStatic(varBinding)) { + // a static field + if (lastBufferChar != '.' && lastBufferChar != '"' && lastBufferChar != '\'') { + // an unescaped and not explicitly qualified name + qualifier = getFinalJ2SClassNameQualifier(null, declaringClass, null, FINAL_ESCAPECACHE) + "."; + } + } else if (declaringClass != null) { + // a nonstatic field + ASTNode parent = node.getParent(); + if (parent == null) { + // not possible? + dumpStack("null parent??"); + } + if (class_shortName == null) { + // not possible? + dumpStack("null class_shortName??"); + } + if (class_shortName != null && lastBufferChar != '.' && parent != null && !(parent instanceof FieldAccess)) + qualifier = getClassNameAndDot(parent, declaringClass, false); + } else { + // declaring class is null -- a local variable or method argument + IMethodBinding meth; + if (class_isAnonymousOrLocal && isFinalOrEffectivelyFinal(varBinding) + && (meth = varBinding.getDeclaringMethod()) != null // not a field + && meth.getDeclaringClass() != class_typeBinding && meth.getKey().indexOf(".lambda$") < 0 + ) { + qualifier = "this.$finals$."; + if (!class_visitedFinalVars.contains(varBinding)) { + class_visitedFinalVars.add(varBinding); + package_htFinalVarToJ2sName.put(varBinding, j2sName); + } + } + } + return qualifier + j2sName; + } + + // different class naming options + private static final int FINAL_RAW = 0; + private static final int FINAL_P = 1; + private static final int FINAL_C = 2; + private static final int FINAL_NEW = 4; + + private static final int FINAL_ESCAPE = 8; + private static final int FINAL_CACHE = 16; + private static final int FINAL_LAMBDA = 32; + private static final int FINAL_STATIC = 64; + private static final int FINAL_PC = FINAL_P | FINAL_C; + private static final int FINAL_ESCAPECACHE = FINAL_ESCAPE | FINAL_CACHE; + + + /** + * Provide access to C$.$clinit$ when a static method is called or a static + * field is accessed. + * + * @param methodQualifier SimpleName qualifier in qualifier.methodName() + * method invocation + * @param declaringJavaClassName the declaring class of the method or variable + * @param flags or of FINAL_ESCAPE except for static nonprivate + * field names; FINAL_CACHE generally true, but + * not for initial class definitions or for some + * nonstatic references + * @return name wrapped if necessary by nested Class.load() calls + */ + String getFinalJ2SClassNameQualifier(Name methodQualifier, ITypeBinding declaringJavaClass, + String declaringJavaClassName, int flags) { + // BH: The idea here is to load these on demand. + // It will require synchronous loading, + // but it will ensure that a class is only + // loaded when it is really needed. + + if (declaringJavaClassName == null) + declaringJavaClassName = getJavaClassNameQualified(declaringJavaClass); + boolean isStatic = ((flags & FINAL_STATIC) == FINAL_STATIC); + boolean doEscape = isStatic || ((flags & FINAL_ESCAPE) == FINAL_ESCAPE); + boolean doCache = ((flags & FINAL_CACHE) == FINAL_CACHE); + String name = removeBracketsAndFixNullPackageName(declaringJavaClassName); + doEscape &= !NameMapper.isClassKnown(name); + if (!doEscape) { + if (methodQualifier != null) { + // a method invocation with a Name as qualifier expression + methodQualifier.accept(this); + return ""; + } + return stripJavaLang(name); + } + if (doCache && name.equals(class_fullName)) { + return "C$"; // anonymous class will be like this + } + + // lambda classes will always be defined at this point. No need to cache them + if (name.indexOf("$lambda") >= 0) + return getFinalJ2SClassName(name, FINAL_P); + return getFinalClazzLoadI$Reference(declaringJavaClass, name, doCache, ((flags & FINAL_NEW) == FINAL_NEW)); + } + + /** + * Turns "test.Test0.Exc1.Exc2" into "['test.Test0','.Exc1','.Exc2']" + * + * For Clazz.newClass we want an array if the superclass is an inner class so + * that the outer class is guaranteed to be loaded first. The same goes for + * $I$[] dynamic class loading and interfaces, (but interfaces are handled + * differently). + * + * @param packageName Java package name or "_" + * @param javaClassName + * @return array listing classes that need to be loaded in order + */ + private String getFinalInnerClassList(ITypeBinding javaClass, String javaClassName) { + String packageName = getPackageName(javaClass); + if (javaClassName == null) { + javaClassName = getJavaClassNameQualified(javaClass); + //System.out.println(">>jcn was null! " + javaClassName); + } + if (javaClassName.indexOf("$lambda") >= 0) + return "'" + getFinalJ2SClassName(javaClassName, FINAL_RAW) + "'"; + String name = removeBracketsAndFixNullPackageName(javaClassName).substring(packageName.length() + 1); + String[] parts = name.split("\\."); + String s = packageName + "." + parts[0]; + int len = parts.length; + String ret = "'" + stripJavaLang(NameMapper.checkClassReplacement(s)) + "'"; + // add inner classes + for (int i = 1; i < len; i++) + ret += ",'." + parts[i] + "'"; + return (ret.indexOf(",") >= 0 ? "[" + ret + "]" : ret); + } + + /** + * Register a qualified static name as an import var I$[n] unless it ends with + * "Exception". Create a loading list for inner classes. + * + * 'pkg.Foo.Bar', for example, becomes ['pkg.Foo','.Bar'] for Clazz.load(). + * + * If caching, put into the code $I$(i,n), where + * + * i is the index into the I$[] + * array, starting at 1 ([0] is reserved for the list of classes we are creating + * here), and + * + * n is 1 if this is a Clazz.new_($I$ call. This flag, if 1, will not run $static$() + * i.e. Java's clinit. + * + * @param javaClassName + * @param doCache + * @return the string to include in the buffer + */ + private String getFinalClazzLoadI$Reference(ITypeBinding javaClass, String javaClassName, boolean doCache, boolean isNew) { + String s = getFinalInnerClassList(javaClass, javaClassName); + if (doCache) { + Integer n = package_htIncludeNames.get(s); + if (n == null && !s.endsWith("Exception'")) { + // count starts at 1, because i$[0] is the list + package_htIncludeNames.put(s, n = new Integer(++package_includeCount[0])); + package_includes.append(package_includeCount[0] == 1 ? ",I$=[[0," : ",").append(s); + } + if (n != null) + return "$I$(" + n + (isNew ? ",1" : "") + ")"; + } + return "Clazz.load(" + s + ")"; + } + + /** + * Get the final JavaScript class name from a Java class name. Options include + * shortening the name with C$. and/or with P$. Brackets are removed. no-package + * names are prepended with "_." "java.lang." is removed where appropriate. + * + * @param javaClassName + * @param flags FINAL_C to allow C$, FINAL_P to allow P$, FINAL_PC for + * both of those; FINAL_RAW to just remove brackets, fix + * null package, and strip java.lang + * @return the final JavaScript name + */ + private String getFinalJ2SClassName(String javaClassName, int flags) { + if (javaClassName == null) + return null; + String name = javaClassName; + name = removeBracketsAndFixNullPackageName(name); + + if (((flags & FINAL_C) != 0)) { + String myJavaClassName = class_fullName; + if (name.equals(myJavaClassName)) { + return "C$"; + } + if (name.startsWith(myJavaClassName + ".")) { + return "C$." + name.substring(myJavaClassName.length() + 1); + } + } + name = stripJavaLang(NameMapper.checkClassReplacement(name)); + return ((flags & FINAL_P) == 0 ? name : checkPackageP$Name(name)); + } + + /** + * strips java.lang, removes brackets, may return P$ + * + * @param thisPackageName null to not allow P$ + * @param javaName + * @return + */ + private String checkPackageP$Name(String javaName) { + return (javaName.startsWith(package_name + ".") ? "P$." + javaName.substring(package_name.length() + 1) + : javaName.equals(package_name) ? "P$" : javaName); + } + + /** + * exp is xxxx or xxxx.yyyy or className.this + * + * @param exp + * @return + */ + private boolean haveDirectStaticAccess(Expression exp) { + return exp instanceof SimpleName + || (exp instanceof QualifiedName && ((QualifiedName) exp).getQualifier() instanceof SimpleName) + || (exp instanceof FieldAccess && ((FieldAccess) exp).getExpression() instanceof ThisExpression); + + } + + /** + * The left operand is primitive boolean. Check to see if the operator is ^, |, + * or &, or if the left or right operand is such an expression. + * + * If so, we are going to box this all as a Boolean(....).valueOf() + * + * @param node + * @return + */ + private boolean isBitwiseBinaryOperator(InfixExpression node) { + if (checkSimpleBooleanOperator(node.getOperator().toString())) { + return true; + } + Expression left = node.getLeftOperand(); + if (left instanceof InfixExpression) { + if (isBitwiseBinaryOperator((InfixExpression) left)) { + return true; + } + } + Expression right = node.getRightOperand(); + if (right instanceof InfixExpression) { + if (isBitwiseBinaryOperator((InfixExpression) right)) { + return true; + } + } + return false; + } + + private void visitList(List list, String separator) { + for (Iterator iter = list.iterator(); iter.hasNext();) { + appendBoxingNode(iter.next(), false); + if (iter.hasNext()) { + buffer.append(separator); + } + } + } + + /** + * Check a class, interface, or Enum binding for generics. + * + * This is used in the method declaration to add alias names to methods. + * + * @param topBinding -- the class being declared + * @param binding + * @return true if this class could have generic replacements + */ + private static boolean checkGenericClass(ITypeBinding topBinding, ITypeBinding binding) { + // debugListAllOverrides(binding); + if (topBinding == binding) + genericClassMap.put(binding.getKey(), null); + // check all superclasses from most super to least super + String classKey = binding.getKey(); + boolean hasGenerics = (binding.isRawType() || binding.getTypeArguments().length > 0); + if (hasSuperClass(binding)) { + hasGenerics = checkGenericClass(topBinding, binding.getSuperclass()) || hasGenerics; + } + // check all interfaces + ITypeBinding[] interfaces = binding.getInterfaces(); + for (int i = interfaces.length; --i >= 0;) { + hasGenerics = checkGenericClass(topBinding, interfaces[i]) || hasGenerics; + } + if (hasGenerics) { + checkMethodsWithGenericParams(topBinding.getKey(), binding); + } else { + genericClassMap.put(classKey, null); + } + return hasGenerics; + } + + /** + * Tie class type parameters (T, V, etc.) to the bound implemented types for all + * methods that implement generics + * + * @param topClassKey + * @param binding + */ + private static void checkMethodsWithGenericParams(String topClassKey, ITypeBinding binding) { + Map classTypes = getGenericClassTypes(binding); + if (classTypes == null) + return; + String classKey = binding.getKey(); + IMethodBinding[] methods = binding.getErasure().getDeclaredMethods(); + for (int i = methods.length; --i >= 0;) { + IMethodBinding m = methods[i]; + String methodName = m.getName(); + ITypeBinding[] params = m.getParameterTypes(); + if (params.length == 0) + continue; + String key = m.getKey(); + if (key.indexOf(";T") >= 0 || key.indexOf("(T") >= 0) { + String[] list = new String[params.length]; + for (int j = list.length; --j >= 0;) { + String name = params[j].getName(); + list[j] = name + "|" + classTypes.get(name) + ";"; + } + addGenericClassMethod(classKey, methodName, list); + addGenericClassMethod(topClassKey, methodName, list); + } + } + + } + + private static ASTNode getAbstractOrAnonymousParentForNode(ASTNode node) { + ASTNode parent = node.getParent(); + while (parent != null && !(parent instanceof AbstractTypeDeclaration) + && !(parent instanceof AnonymousClassDeclaration)) { + parent = parent.getParent(); + } + return parent; + } + + /** + * Create a map of the class type arguments for an implemented generic class + * + * @param type + * @return a map {T:"java.lang.String",K:"java.lang.Object"} + */ + private static Map getGenericClassTypes(ITypeBinding type) { + String classKey = type.getKey(); + Map classTypes = genericClassTypes.get(classKey); + if (classTypes != null) + return classTypes; + ITypeBinding[] typeArgs = type.getTypeArguments(); + ITypeBinding[] typeParams = type.getTypeParameters(); + boolean isGeneric = (typeParams.length > 0); + boolean isExtended = (typeArgs.length > 0 || type.isRawType()); + if (!isGeneric && !isExtended) { + if (hasSuperClass(type)) + genericClassTypes.put(classKey, classTypes = genericClassTypes.get(type.getSuperclass().getKey())); + return classTypes; + } + ITypeBinding[] types = (isGeneric ? typeParams : typeArgs); + classTypes = new LinkedHashMap(); + // We have to parse this by hand, because I cannot seem to get access to + // the + // typeParameters of a superclass. Java seems to have erased all that. + String erasure = type.getErasure().toString(); + // abstract class test.Test_GenericExt_T, K> + erasure = erasure.substring(erasure.indexOf("<") + 1); + StringBuffer sb = new StringBuffer(erasure.substring(0, erasure.indexOf(">\n"))); + for (int n = 0, i = sb.length(); --i >= 0;) { + switch (sb.charAt(i)) { + case '>': + n++; + sb.setCharAt(i, ' '); + break; + case '<': + n--; + sb.setCharAt(i, ' '); + break; + case ',': + if (n != 0) + sb.setCharAt(i, ' '); + break; + default: + break; + } + } + + String[] tokens = sb.toString().split(","); + for (int i = tokens.length; --i >= 0;) { + String key = tokens[i].trim(); + key = key.substring(0, (key + " ").indexOf(" ")); + String value = (i < types.length ? getJavaClassNameQualified(types[i]) : "java.lang.Object"); + classTypes.put(key, value); + } + return classTypes; + } + + /** + * Retrieve a list of generic types such as { ["T|java.lang.String", + * "V|java.lang.Object"], ["M|java.lang.String", "N|java.lang.Object"] } if it + * exists + * + * @param methodClass + * @param methodName + * @return list of generic types for methods with this name + */ + private static List getGenericMethodList(ITypeBinding methodClass, String methodName) { + Map> methodList = genericClassMap.get(methodClass.getKey()); + return (methodList == null ? null : methodList.get(methodName)); + } + + /** + * add a generic class method to the genericClassMap under the class and method + * + * @param classKey + * @param methodName + * @param list + */ + private static void addGenericClassMethod(String classKey, String methodName, String[] list) { + + Map> classMap = genericClassMap.get(classKey); + if (classMap == null) + genericClassMap.put(classKey, classMap = new Hashtable>()); + List methodList = classMap.get(methodName); + if (methodList == null) + classMap.put(methodName, methodList = new ArrayList()); + methodList.add(list); + } + + /** + * + * This is the method used to get the name or names to write into the method + * declaration Clazz.newMeth(...). Bracketed returns tell Clazz to create + * multiple aliases for the same method. + * + * @param node + * @param mBinding + * @param isConstructor + * @param mode + * @return j2s-qualified name or an array of j2s-qualified names + */ + String getFinalMethodNameOrArrayForDeclaration(IMethodBinding mBinding, boolean isConstructor, + int mode) { + String nodeName = mBinding.getName(); + String methodName = (isConstructor ? "c$" : nodeName); + String qname = getFinalMethodNameWith$Params(methodName, null, mBinding, null, false, METHOD_NOTSPECIAL); + ITypeBinding methodClass = mBinding.getDeclaringClass(); + List names = null; + List methodList = getGenericMethodList(methodClass, nodeName); + + if (methodList != null) { + names = new ArrayList(); + for (int i = methodList.size(); --i >= 0;) { + String pname = getFinalMethodNameWith$Params(methodName, null, mBinding, methodList.get(i), false, + METHOD_NOTSPECIAL); + if (pname != null) + names.add(pname); + if ((mode & METHOD_FULLY_QUALIFIED) == 0) + names.add(ensureMethod$Name(methodName, mBinding, getJavaClassNameQualified(methodClass))); + } + } + if ((mode & METHOD_$_QUALIFIED) != 0 && !methodName.equals(qname) + && !classHasNoParameterMethod(methodClass, methodName)) { + if (names == null) + names = new ArrayList(); + names.add(methodName + (methodName.indexOf("$") >= 0 ? "" : methodName.equals("c") ? "$$" : "$")); + } + if ((mode & METHOD_UNQUALIFIED) != 0) { + if (names == null) + names = new ArrayList(); + names.add(methodName); + } + if (names == null || names.size() == 0 || mode == METHOD_FULLY_QUALIFIED_JUST_ONE) + return "'" + qname + "'"; + qname = ",'" + qname + "'"; + for (int i = names.size(); --i >= 0;) { + String next = ",'" + names.get(i) + "'"; + if (qname.indexOf(next) < 0) + qname += next; + } + return "[" + qname.substring(1) + "]"; + } + + private static boolean classHasNoParameterMethod(ITypeBinding methodClass, String methodName) { + while (methodClass != null) { + IMethodBinding[] methods = methodClass.getDeclaredMethods(); + for (int i = methods.length; --i >= 0;) { + IMethodBinding m = methods[i]; + if (m.getName().equals(methodName) && m.getParameterTypes().length == 0 && !isPrivate(m)) + return true; + } + methodClass = methodClass.getSuperclass(); + } + return false; + } + + /** + * Add postfix "$" is the method is: + * + * (a) static or not private, and + * + * (b) there is no field name that covers it, and + * + * (c) it doesn't already have a $ in it after the 3rd character (P$..., C$... + * excepted), and + * + * (d) the name is not "c$", which is reserved for function(s) { s.charCodeAt(0) + * }, and + * + * (e) the method is not an explicitly nonqualified method (namely, toString()) + * or in an explicitly nonqualified class + * + * @param j2sName + * @param mBinding + * @param className + * @return + */ + private static String ensureMethod$Name(String j2sName, IMethodBinding mBinding, String className) { + if (isPrivate(mBinding) && !isStatic(mBinding) + || NameMapper.fieldNameCoversMethod(j2sName) + || j2sName.indexOf("$", 2) >= 0 + || j2sName.equals("c$") + || className != null && NameMapper.isMethodNonqualified(className, mBinding.getName())) + return j2sName; + // c() must be changed to c$$, not c$, which is the constructor + return (j2sName.equals("c") ? "c$$" : j2sName + "$"); + } + + /** + * finish the generic foo || bar fix + * + * @param pt start of this method invocation in buffer + * @param qname qualified name, containing " || " + * @param isPrivateAndNotStatic switch $O$ to p$; already using .apply(this) + */ + private void postFixGeneric$OMethodName(int pt, String qname, boolean isPrivateAndNotStatic, String privateVar) { + // this is a Java8-compatibility hack. The class is accessing a + // type-parameterized method which it might not be overriding + // and might be nongeneric in Java 6. + // this.adItem$TE(o) becomes (($o$=this).addItem$TE || + // $o$.addItem$O).apply($o$,[o]); + if (isPrivateAndNotStatic) { + buffer.insert(pt, "("); + buffer.append(qname.replace("$o$", privateVar)).append(")"); + return; + } + buffer.insert(pt, "(($o$="); + buffer.append(qname).append(").apply($o$,["); + buffer.insert(buffer.lastIndexOf(".", buffer.lastIndexOf("|")), ")"); + trailingBuffer.addType("o"); + } + + private boolean isJava(String className) { + return className.length() > 5 && "java.javax".contains(className.substring(0, 5)); + } + + private String getParamsAsString(int nParams, String[] genericTypes, ITypeBinding[] paramTypes, + boolean toObject) { + StringBuffer sbParams = new StringBuffer(); + // if this is a method invocation and has generics, then we alias that + boolean haveGeneric = false; + for (int i = 0; i < nParams; i++) { + String type = j2sGetParamCode(paramTypes[i], true, toObject); + bufferDebug("getParamsAsString " + type + " " + genericTypes + " " + i + " " + nParams); + if (genericTypes != null) { + String genericType = genericTypes[i]; + if (genericType != null) { + if (genericType.indexOf("|null") < 0) { + if (genericType.indexOf("|" + getJavaClassNameQualified(paramTypes[i]) + ";") < 0) + return null; + type = "T" + genericType.substring(0, genericType.indexOf("|")); // "O";// + haveGeneric = true; + // Originally I was substituting in the generic type + // T,V,E,etc., but + // this causes a problem when the user is working with a + // later version of + // Java and subclassing what was originally not a + // generic class (JComboBox) + // but which is now generic (JComboBox). The new + // version of Java will be + // used by the transpiler working on the user's machine, + // and then we will + // have the problem that the code will have addItem$TE + // inserted even though + // the version of Java in the SwingJS distribution will + // be only addItem$O. + // Using Object here because that would be the default + // for + // JComboBox<> + // and so match that earlier non-generic designation + // (hopefully). + } + } + } + sbParams.append("$").append(type); + } + return (toObject && !haveGeneric ? null : sbParams.toString()); + } + + private static String j2sGetParamCode(ITypeBinding binding, boolean addAAA, boolean asGenericObject) { + String prefix = (removeBracketsAndFixNullPackageName(binding.getKey()).indexOf(":T") >= 0 ? "T" : null); + String name = removeBrackets(getJavaClassNameQualified(binding)); + if (!asGenericObject && prefix == null && !binding.isPrimitive()) + name = NameMapper.fixPackageName(name); + String arrays = null; + int pt = name.indexOf("["); + if (pt >= 0) { + arrays = name.substring(pt + (name.indexOf("[L") >= 0 ? 1 : 0)); + name = name.substring(0, pt); + } + + // NOTE: If any of these are changed, they must be changed in j2sSwingJS + // as well. + // NOTE: These are the same as standard Java Spec, with the exception of + // Short, which is "H" instead of "S" + + switch (name) { + case "boolean": + name = "Z"; + break; + case "byte": + name = "B"; + break; + case "char": + name = "C"; + break; + case "double": + name = "D"; + break; + case "float": + name = "F"; + break; + case "int": + name = "I"; + break; + case "long": + name = "J"; + break; + case "short": + name = "H"; // differs from Java Spec so we can use S for String + break; + case "java.lang.Object": + case "Object": + name = "O"; + break; + case "java.lang.String": + name = "S"; + break; + default: + if (prefix == null) + name = NameMapper.checkClassReplacement(name); + else + name = (asGenericObject ? "O" : prefix + name); // "O";// + name = name.replace("java.lang.", "").replace('.', '_'); + break; + } + if (arrays != null) { + if (addAAA) + arrays = arrays.replaceAll("\\[\\]", "A"); + name += arrays; + } + return name; + } + + /** + * Remove <...> in class and method names + * + * @param qName + * @return + */ + static String removeBracketsAndFixNullPackageName(String qName) { + if (qName == null) + return null; + qName = NameMapper.fixPackageName(qName); + return removeBrackets(qName); + } + + private static String removeBrackets(String qName) { + if (qName.indexOf('<') < 0) + return qName; + StringBuffer buf = new StringBuffer(); + int ltCount = 0; + char c; + for (int i = 0, len = qName.length(); i < len; i++) { + switch (c = qName.charAt(i)) { + case '<': + ltCount++; + continue; + case '>': + ltCount--; + continue; + default: + if (ltCount == 0) + buf.append(c); + continue; + } + } + return buf.toString().trim(); + } + + /** + * Returns true if the given type is a super type of a candidate. + * true is returned if the two type bindings are identical + * + * @param possibleSuperType the type to inspect + * @param type the type whose super types are looked at + * @return true iff possibleSuperType is a super type + * of type or is equal to it + */ + private static boolean isSuperType(ITypeBinding possibleSuperType, ITypeBinding type) { + if (type.isArray() || type.isPrimitive()) { + return false; + } + String name; + if (possibleSuperType.isEqualTo(type) + || (name = possibleSuperType.getBinaryName()) != null && name.equals(type.getBinaryName())) + return true; + ITypeBinding superClass = type.getSuperclass(); + if (superClass != null && isSuperType(possibleSuperType, superClass)) + + return true; + + if (possibleSuperType.isInterface()) { + ITypeBinding[] superInterfaces = type.getInterfaces(); + for (int i = 0; i < superInterfaces.length; i++) { + if (isSuperType(possibleSuperType, superInterfaces[i])) { + return true; + } + } + } + return false; + } + + private static boolean hasSuperClass(ITypeBinding typeBinding) { + ITypeBinding superclass = typeBinding.getSuperclass(); + return (superclass != null && !"java.lang.Object".equals(superclass.getQualifiedName())); + } + + private Object getConstant(Expression exp) { + return (noDocProblem(exp) ? exp.resolveConstantExpressionValue() : null); + } + + private boolean noDocProblem(Expression exp) { + return (getJ2sJavadoc(exp, DOC_CHECK_ONLY) == null + || !(exp instanceof PrefixExpression || exp instanceof InfixExpression + || exp instanceof PostfixExpression || exp instanceof ParenthesizedExpression)); + } + + /** + * If given expression is constant value expression, return its value string; or + * character or return null. + * + * @param node + * @return + */ + private boolean getConstantValue(Expression node, boolean andWrite) { + if (node == null) + return false; + Object constValue = getConstant(node); + StringBuffer sb = null; + if (constValue instanceof Number) { + if (!andWrite) + return true; + sb = new StringBuffer(); + String s = constValue.toString(); + if (s.startsWith("-") && buffer.charAt(buffer.length() - 1) == '-') + sb.append(' '); + sb.append(s); + } else if (constValue instanceof Character || constValue instanceof Boolean) { + if (!andWrite) + return true; + sb = new StringBuffer(); + if (constValue instanceof Character) { + sb.append('"'); + addChar(((Character) constValue).charValue(), sb); + sb.append('"'); + } else { + sb.append(constValue); + } + } else if (constValue instanceof String) { + if (!andWrite) + return true; + sb = new StringBuffer(); + addString((String) constValue, sb); + } + if (sb == null) + return false; + if (andWrite) { + // this is just in case we have (/** @j2sNative 1?x:*/y); + boolean needParen = (node instanceof ParenthesizedExpression); + if (needParen) + buffer.append("("); + addJ2SDoc(node); + buffer.append(sb); + if (needParen) + buffer.append(")"); + } + return true; + } + + private void addString(String str, StringBuffer sb) { + int length = str.length(); + sb.append('"'); + for (int i = 0; i < length; i++) + addChar(str.charAt(i), sb); + sb.append('"'); + } + + private static void addChar(char c, StringBuffer buffer) { + switch (c) { + case '\\': + case '\'': + case '\"': + buffer.append('\\'); + buffer.append(c); + break; + case '\r': + buffer.append("\\r"); + break; + case '\n': + buffer.append("\\n"); + break; + case '\t': + buffer.append("\\t"); + break; + case '\f': + buffer.append("\\f"); + break; + default: + if (c < 32 || c > 127) { + String hexStr = "0000" + Integer.toHexString(c); + buffer.append("\\u").append(hexStr.substring(hexStr.length() - 4)); + } else { + buffer.append(c); + } + break; + } + } + +//////////////////////// + + private void setMapJavaDoc(PackageDeclaration node) { + ASTNode root = node.getRoot(); + package_mapBlockJavadoc = new HashMap>(); + + // gat a list of all @j2s blocks + + List list = new ArrayList(); + List commentList = ((CompilationUnit) root).getCommentList(); + for (int i = 0, n = commentList.size(); i < n; i++) { + Comment comment = (Comment) commentList.get(i); + if (comment instanceof Javadoc) { + List tags = ((Javadoc) comment).tags(); + if (tags.size() != 0) { + for (Iterator itr = tags.iterator(); itr.hasNext();) { + TagElement tagEl = (TagElement) itr.next(); + String tagName = tagEl.getTagName(); + if (tagName == null || !tagName.startsWith("@j2sNative") && !tagName.startsWith("@j2sIgnore") + && !tagName.startsWith("@j2sDebug")) + continue; + list.add(comment); + break; + } + } + } + } + if (list.isEmpty()) + return; + + // now add all the associated elements + + try { + root.accept(new NativeDoc.BlockVisitor(list)); + } catch (@SuppressWarnings("unused") IndexOutOfBoundsException e) { + // normal termination from item after last j2sjavadoc + } + // and link javadoc to its closest block + + for (int i = 0, n = list.size() - 1; i < n;) { + Javadoc doc = (Javadoc) list.get(i++); + ASTNode item = list.get(i); + int factor = 1; + if (item instanceof Javadoc) { + logErr("!!Note: @j2s doc ignored because nothing follows it: " + doc.getStartPosition() + "\n" + doc); + } else { + if (item == null) { + factor = -1; + item = list.get(++i); + } + i++; + Integer pt = Integer.valueOf(item.getStartPosition() * factor); + List docs = package_mapBlockJavadoc.get(pt); + if (docs == null) + package_mapBlockJavadoc.put(pt, docs = new ArrayList()); + docs.add(doc); + } + } + } + + /** + * check any node other than the package node for @j2sNative or @j2sDebug + * or @j2sIgnore + */ + public boolean preVisit2(ASTNode node) { + return (node instanceof ParenthesizedExpression || !addJ2SDoc(node) || !(node instanceof Block)); + } + + private final static int DOC_CHECK_ONLY = 0; + @SuppressWarnings("unused") + private final static int DOC_ADD_PRE = 1; + private final static int DOC_ADD_POST = 2; + + /** + * + * @param node + * @return true if code was added + */ + private boolean addJ2SDoc(ASTNode node) { + List j2sJavadoc; + if (package_mapBlockJavadoc == null || node instanceof MethodDeclaration || node instanceof Initializer + || (j2sJavadoc = getJ2sJavadoc(node, DOC_CHECK_ONLY)) == null || node instanceof InfixExpression + && ((InfixExpression) node).getLeftOperand() instanceof ParenthesizedExpression) + return false; + boolean ret = NativeDoc.addJ2sJavadocs(buffer, j2sJavadoc, node instanceof Block); + j2sJavadoc.clear(); + return ret; + } + + private List getJ2sJavadoc(ASTNode node, int mode) { + + // package_mapBlockJavadoc will be null for a no-package class like VARNA.java, + // which are not allowed to use @j2sNative + + if (package_mapBlockJavadoc == null) + return null; + List docs; + if (mode == DOC_ADD_POST) { + docs = package_mapBlockJavadoc.remove(Integer.valueOf(-1 * node.getStartPosition())); + if (docs != null) + NativeDoc.addJ2sJavadocs(buffer, docs, false); + } else { + docs = package_mapBlockJavadoc.get(Integer.valueOf(node.getStartPosition())); + } + return docs; + } + + + /** + * Method with "j2s*" tag. + * + * @param node + * @return false if we have @j2sIngore for this BodyDeclaration + */ + private boolean checkAnnotations(BodyDeclaration node, int mode) { + if (mode != CHECK_ANNOTATIONS_ONLY) { + Javadoc javadoc = node.getJavadoc(); + if (javadoc != null) { + List tags = javadoc.tags(); + if (tags.size() != 0) { + for (Iterator iter = tags.iterator(); iter.hasNext();) { + TagElement tagEl = (TagElement) iter.next(); + if ("@j2sIgnore".equals(tagEl.getTagName())) { + return false; + } + } + } + } + } + List modifiers = node.modifiers(); + if (modifiers != null && modifiers.size() > 0) { + + for (Iterator iter = modifiers.iterator(); iter.hasNext();) { + Object obj = iter.next(); + if (obj instanceof Annotation) { + if (!addAnnotation((Annotation) obj, node, mode)) + return false; + } + } + } + return true; + } + + private boolean addAnnotation(Annotation annotation, ASTNode node, int mode) { + String name = annotation.getTypeName().getFullyQualifiedName(); + int idx = name.indexOf("J2S"); + if (idx >= 0) { + return (mode == CHECK_ANNOTATIONS_ONLY || !name.substring(idx).startsWith("J2SIgnore")); + } + if (global_ignoredAnnotations == null || global_ignoredAnnotations.indexOf(";" + name + ";") >= 0) { + return true; + } + if (class_annotations == null) + class_annotations = new ArrayList(); + String qname = getFinalJ2SClassName(annotation.resolveTypeBinding().getQualifiedName(), FINAL_RAW); + class_annotations.add(new ClassAnnotation(qname, annotation, node)); + if (node instanceof TypeDeclaration) { + try { + throw new NullPointerException(); + } catch (Exception e) { + e.printStackTrace(); + } + //class_hasTypeAnnotations = true; + } + if (name.startsWith("Xml")) { + if ("XmlAccessorType".equals(name)) { + String s = annotation.toString(); + class_annotationType = (s.contains("FIELD") ? JAXB_TYPE_FIELD + : s.contains("PUBLIC") ? JAXB_TYPE_PUBLIC_MEMBER + : s.contains("PROPERTY") ? JAXB_TYPE_PROPERTY : JAXB_TYPE_NONE); + } else if (name.startsWith("XmlEnum")) { + class_annotationType = JAXB_TYPE_ENUM; + } else if (class_annotationType == ANNOTATION_TYPE_UNKNOWN && name.startsWith("Xml")) { + class_annotationType = JAXB_TYPE_UNSPECIFIED; + } else if ("XmlElements".equals(name) && annotation.isSingleMemberAnnotation()) { + Expression e = ((SingleMemberAnnotation) annotation).getValue(); + if (e instanceof ArrayInitializer) { + @SuppressWarnings("unchecked") + List expressions = ((ArrayInitializer) e).expressions(); + for (int i = expressions.size(); --i >= 0;) { + Expression exp = expressions.get(i); + if (exp instanceof Annotation) { + qname = getFinalJ2SClassName(((Annotation) exp).resolveTypeBinding().getQualifiedName(), + FINAL_RAW); + class_annotations.add(new ClassAnnotation(qname, (Annotation) exp, node)); + } + } + } + } + } else if (class_annotationType == ANNOTATION_TYPE_UNKNOWN) { + class_annotationType = NOT_JAXB; + } + return true; + } + + ///////////////////////////// + + public static void setLogging(List lstMethodsDeclared, Map htMethodsCalled, + boolean logAllCalls) { + global_lstMethodsDeclared = lstMethodsDeclared; + global_htMethodsCalled = htMethodsCalled; + global_logAllCalls = logAllCalls; + if (lstMethodsDeclared != null) + lstMethodsDeclared.clear(); + if (logAllCalls) + htMethodsCalled.clear(); + } + + private void logMethodDeclared(String name) { + if (name.startsWith("[")) { + String[] names = name.substring(0, name.length() - 1).split(","); + for (int i = 0; i < names.length; i++) + logMethodDeclared(names[i]); + return; + } + String myName = fixLogName(class_fullName); + if (name.startsWith("'")) + name = name.substring(1, name.length() - 1); + global_lstMethodsDeclared.add(myName + "." + name); + } + + private void logMethodCalled(String name) { + name = fixLogName(name); + String myName = fixLogName(class_fullName); + if (global_logAllCalls) + global_htMethodsCalled.put(name + "," + myName, "-"); + else + global_htMethodsCalled.put(name, myName); + } + + private String fixLogName(String name) { + name = NameMapper.checkClassReplacement(name); + int pt = name.indexOf("<"); + return (pt > 0 ? name.substring(0, pt) : name); + } + + /** + * tracks file byte pointers for @j2sNative, @j2sIgnore + */ + private Map> package_mapBlockJavadoc; + + /** + * separates top-level classes found in a source file + * + */ + private static final String ELEMENT_KEY = "__@J2S_ELEMENT__"; + + /** + * Add the top-level class name with the element key. + * + * @param className + */ + private void appendElementKey(String className) { + buffer.append(ELEMENT_KEY + ("=" + className) + "\n"); + } + + /** + * Separate the buffer into a list so that all top-level elements can be in + * their own file (as is done in Java). Provide a common include list + * + * We do not have to worry about inner classes, as they are never referenced + * directly. + * + * @return List {elementName, js, elementName, js, ....} + */ + public List getElementList() { + if (class_annotationType != ANNOTATION_TYPE_UNKNOWN) { + addDummyClassForPackageOnlyFile(); + } + + String trailer = ";Clazz.setTVer('" + VERSION + "');//Created " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " Java2ScriptVisitor version " + + VERSION + " net.sf.j2s.core.jar version " + CorePlugin.VERSION + "\n"; + List elements = new ArrayList(); + String js = buffer.toString(); + String eq = "="; // because we might be operating on this file + String[] parts = js.split(ELEMENT_KEY + eq); + String header = parts[0]; + String header_noIncludes = header.replace(",I$=[[]]", ""); + header = header.replace(",I$=[]", privateVarString + (package_includes.length() == 0 ? "" + : package_includes.append("]]," + + "$I$=function(i,n){return" + + "(i=(I$[i]||(I$[i]=Clazz.load(I$[0][i]))))," + + "!n&&i.$load$&&Clazz.load(i,2)," + + "i}" + ))); + for (int i = 1; i < parts.length; i++) { + js = parts[i]; + int pt = js.indexOf("\n"); + String name = js.substring(0, pt); + elements.add(name); + js = js.substring(pt + 1); + String head = "(function(){" + + (js.indexOf("$I$(") < 0 && js.indexOf("p$") < 0 ? header_noIncludes : header); + elements.add(head + js + "})();\n" + trailer); + } + resetPrivateVars(); + return elements; + } + + private void addDummyClassForPackageOnlyFile() { + appendElementKey("_$"); + buffer.append("var C$=Clazz.newClass(\"_$\");"); + appendClinit(); + ClassAnnotation.addClassAnnotations(this, class_annotationType, class_annotations, null, null, null, null, + trailingBuffer); + buffer.append(trailingBuffer); + addDefaultConstructor(); + } + + public boolean visit(AnnotationTypeDeclaration node) { + addClassOrInterface(node, node.resolveBinding(), node.bodyDeclarations(), '@'); + return false; + } + + public boolean visit(BlockComment node) { + return false; + } + + public boolean visit(ImportDeclaration node) { + return false; + } + + public boolean visit(Javadoc node) { + return false; + } + + public boolean visit(LineComment node) { + return false; + } + + public boolean visit(MarkerAnnotation node) { + return false; + } + + public boolean visit(MemberRef node) { + return false; + } + + public boolean visit(MemberValuePair node) { + return false; + } + + public boolean visit(MethodRef node) { + return false; + } + + public boolean visit(MethodRefParameter node) { + return false; + } + + public boolean visit(NormalAnnotation node) { + return false; + } + + public boolean visit(ParameterizedType node) { + node.getType().accept(this); + return false; + } + + public boolean visit(SingleMemberAnnotation node) { + return false; + } + + public boolean visit(TagElement node) { + return false; + } + + public boolean visit(TextElement node) { + return false; + } + + public boolean visit(TypeParameter node) { + return false; + } + + public boolean visit(WildcardType node) { + return false; + } + +// /** +// * Prepend a $ to a local var name if there is a JavaScript keyword collision. +// * Either a local variable or a method invocation parameter. +// * +// * @param localName +// * @return localName or $localName +// */ +// private static String getFinalVarName(String localName) { +// return NameMapper.getJavaScriptCollisionIdentifier(localName, true); +// } + + private static String getFinalFieldName(IVariableBinding binding) { + return getFinalFieldOrLocalVariableName(binding.getDeclaringClass(), binding.getName()); + } + + /** + * Prepend a $ to a field name if there is a JavaScript keyword collision, and n + * more if there are collisions with higher-level field names. + * + * This method no longer checks for field-method collisions. + * + * @param classBinding + * @param fieldName + * @param isPrivate + * @return + */ + private static String getFinalFieldOrLocalVariableName(ITypeBinding classBinding, String fieldName) { + String js$ = NameMapper.getJavaScriptCollisionIdentifier(fieldName, false); + return (isJ2SInheritedFieldName(classBinding, fieldName) ? NameMapper.getJ2S$$InheritedFieldName(classBinding, + fieldName, NameMapper.newFieldNameBuf(fieldName, new StringBuffer(js$))).toString() : js$ + fieldName); + } + + /** + * Iteratively check whether the given field name is already defined in a stack + * of classes or interfaces. + * + * The algorithm: + * + * 1. Check self (class or interface) + * + * 2. Check super class + * + * 3. Check interfaces + * + * @param binding + * @param name + * @return + */ + static boolean isJ2SInheritedFieldName(ITypeBinding binding, String name) { + + if (binding == null) + return false; + + if ("serialVersionUID".equals(name)) { + // ignore + return false; + } + + IVariableBinding[] declaredFields; + ITypeBinding superclass = binding.getSuperclass(); + if (superclass == null) { + // interface + declaredFields = binding.getDeclaredFields(); + } else { + declaredFields = superclass.getDeclaredFields(); + } + for (int i = 0; i < declaredFields.length; i++) { + if (name.equals(declaredFields[i].getName())) { + return true; + } + } + if (superclass != null && isJ2SInheritedFieldName(superclass, name)) { + return true; + } + ITypeBinding[] interfaces = binding.getInterfaces(); + if (interfaces != null) { + for (int i = 0; i < interfaces.length; i++) { + if (isJ2SInheritedFieldName(interfaces[i], name)) { + return true; + } + } + } + return false; + } + + /** + * This is the principle method for returning a fully qualified method name. x + * Determine the qualified parameter suffix for method names, including + * constructors. Now returns name$ for all unparameterized methods not + * explicitly excluded. + * + * @param javaClassNameForInvocation + * @param mBinding + * @param genericTypes only in the case of method declarations, + * where we are trying to match generic + * methods + * @param addCallingOption$O + * @param isLiteralOrLambda_C + * + * @return a fully j2s-qualified method name + */ + private String getFinalMethodNameWith$Params(String j2sName, String javaClassNameForInvocation, + IMethodBinding mBinding, String[] genericTypes, boolean addCallingOption$O, int specialType) { + // The problem is that System.out and System.err are PrintStreams, and + // we + // do not intend to change those. So in the case that we just wrote + // "System....", we use that instead and do not qualify the name + // Note: binding can be null if we have errors in the Java and we are + // compiling + // + // mBinding should never be null, but... + if (mBinding == null) + return j2sName; + String methodName = mBinding.getName(); + if (j2sName == null) + j2sName = methodName; + ITypeBinding declaringClass = mBinding.getDeclaringClass(); + String javaClassName = getJavaClassNameQualified(declaringClass); + if (NameMapper.isMethodNonqualified(javaClassName, methodName)) + return j2sName; + + // BH: Note in the next statement, that Map.put$K$V is translated to actual + // values + // if .getMethodDeclaration() is not used. + // Without that, it uses the bound parameters such as + // String, Object instead of the declared ones, such as $TK$TV + + // !isMethodInvoaction and isLiteralOrLambda_C means LambdaC + ITypeBinding[] paramTypes = (specialType == METHOD_LAMBDA_C ? mBinding.getParameterTypes() + : mBinding.getMethodDeclaration().getParameterTypes()); + + int nParams = paramTypes.length; + if (genericTypes != null && genericTypes.length != nParams) + return null; + + // xxx() adds $ to become xxx$() iff it is NOT + // - (private and not static) + // - toString or hashCode + // - already qualified with $ + // skipping C$. + if (nParams == 0) + return ensureMethod$Name(j2sName, mBinding, null); + + // functional interface methods are qualified only by "$", not their parameters. + // This is not ideal. + + IMethodBinding fm = (specialType != METHOD_NOTSPECIAL || javaClassName.equals("java.lang.reflect.Proxy") ? null + : declaringClass.getFunctionalInterfaceMethod()); + if (fm != null && methodName.equals(fm.getName())) + return ensureMethod$Name(j2sName, mBinding, null); + + String s = getParamsAsString(nParams, genericTypes, paramTypes, false); + + if (addCallingOption$O && s.indexOf("$T") >= 0 && isJava(javaClassName) && !isJava(class_fullName)) { + // If the method being called is a Java class and the calling class is NOT a + // Java class, + // then also add the $O version. + String generic = getParamsAsString(nParams, genericTypes, paramTypes, true); + if (generic != null) { + trailingBuffer.addType("o"); + return j2sName + s + " || $o$." + j2sName.substring(j2sName.lastIndexOf(".") + 1) + generic; + } + // this does not work for two reasons: + // 1) sometimes the qualifier, so for t.foo$TA(o), "t." is outside + // the scope of these parentheses. + // 2) When selecting functions like this, one needs to use apply, + // so: ((a$ = expression).foo$TA || a$.foo$O).apply(a$, [o]) + // + // thus, this determination must be made very early. + + } + + return j2sName + s; + } + + public static class NameMapper { + + /** + * defined already in j2sClazz.js + */ + static String[] j2sClazzPackages = { "java.lang", "java.lang.reflect", "java.io", "java.util" }; + + static boolean isJ2sClazzPackage(String packageName) { + for (int i = 0; i < j2sClazzPackages.length; i++) + if (j2sClazzPackages[i].equals(packageName)) + return true; + return false; + } + + static final String primitiveTypeEquivalents = "Boolean,Byte,Character,Short,Integer,Long,Float,Double,Void,"; + static final String noConstructorNames = ",Boolean,Byte,Short,Integer,Long,Float,Double,"; + + static String fixPackageName(String name) { + // trying to avoid "double" or "doubleA" here. Not perfect, but who would + // ever name a top-level class with a lower-case starting letter? + // a name can be "" here as for LambdaExpressions + return (name.length() > 0 && name.indexOf(".") < 0 && !Character.isLowerCase(name.charAt(0)) + ? NULL_PACKAGE + "." + name + : name); + } + + static boolean isOneOf(String key, String values) { + return (values.indexOf("," + key + ",") >= 0); + } + + static String getPrimitiveTYPE(String name) { + int pt = primitiveTypeEquivalents.indexOf(name.substring(1)) - 1; + String type = primitiveTypeEquivalents.substring(pt); + return type.substring(0, type.indexOf(",")); + } + + private final static String[] knownClasses = new String[] { "java.lang.Object", "java.lang.Class", + "java.lang.String", "java.lang.Byte", "java.lang.Character", "java.lang.Short", "java.lang.Long", + "java.lang.Integer", "java.lang.Float", "java.lang.Double", "java.io.Serializable", + "java.lang.Iterable", "java.lang.CharSequence", "java.lang.Cloneable", "java.lang.Comparable", + "java.lang.Runnable", "java.lang.System", "java.lang.ClassLoader", "java.lang.Math", + "java.lang.Number" }; + private final static Set knownClassHash = new HashSet(); + static { + for (int i = knownClasses.length; --i >= 0;) + knownClassHash.add(knownClasses[i]); + } + + static boolean isClassKnown(String qualifiedName) { + return knownClassHash.contains(qualifiedName); + } + + public static void setClassReplacements(String keyValues) { + // j2s.class.replacements=org.apache.log4j.*:jalview.jslogger.; + htClassReplacements = null; + if (keyValues == null) + return; + htClassReplacements = new Hashtable(); + lstPackageReplacements = new ArrayList(); + String[] pairs = keyValues.split(";"); + for (int i = pairs.length; --i >= 0;) { + pairs[i] = pairs[i].trim(); + if (pairs[i].length() == 0) + continue; + String[] kv = pairs[i].split("->"); + htClassReplacements.put(kv[0], kv[1]); + if (kv[0].endsWith(".")) + lstPackageReplacements.add(kv[0]); + log("class replacement " + kv[0] + " --> " + kv[1]); + } + } + + static String checkClassReplacement(String className) { + if (htClassReplacements != null) { + String rep = htClassReplacements.get(className); + if (rep == null && lstPackageReplacements != null) { + for (int i = lstPackageReplacements.size(); --i >= 0;) { + rep = lstPackageReplacements.get(i); + if (className.startsWith(rep)) { + rep = htClassReplacements.get(rep) + className.substring(rep.length()); + break; + } + if (i == 0) + rep = null; + } + + } + if (rep != null) { + log(className + " -> " + rep); + return rep; + } + } + return className; + } + + /** + * classes and packages that do not accept $ in their method names + * + */ + private final static String defaultNonQualified + // Math and Date both are minor extensions + // of JavaScript, so they are not qualified + = //"java.lang.Math;" + + // MAYBE NOT! + "java.util.Date;" + // swingjs.api.js and javajs.api.js contain + // interfaces to JavaScript methods and so + // are not parameterized. + + "*.api.js;" + // netscape.JSObject interface includes 8 methods + // that do not need to be parameterized. + // + "netscape.*;" + ; + + private static String[] nonQualifiedPackages; + + /** + * .j2s option j2s.compiler.nonqualified.packages/classes + * + * @param names semicolon-separated list. For example, + * org.jmol.api.js;jspecview.api.js + */ + public static void setNonQualifiedNamePackages(String names) { + names = defaultNonQualified + (names == null ? "" : names); + nonQualifiedPackages = names.replace(";;", ";").trim().split(";"); + for (int i = nonQualifiedPackages.length; --i >= 0;) { + String s = nonQualifiedPackages[i]; + if (s.length() == 0) + continue; + if (s.startsWith("*.")) + s = s.substring(1); + if (s.endsWith(".")) + s = s.substring(0, s.length() - 1); + nonQualifiedPackages[i] = (s.endsWith("*") ? s.substring(0, s.length() - 1) : s + ".").trim(); + } + } + + /** + * Check to see if this class is in a package for which we exclude parameter + * qualification + * + * @param className + * @return + */ + private static boolean isPackageOrClassNonqualified(String className) { + className += "."; + for (int i = nonQualifiedPackages.length; --i >= 0;) { + String s = nonQualifiedPackages[i]; + if (s.length() > 0 && s.startsWith(".") ? className.contains(s) : className.startsWith(s)) { + return true; + } + } + return false; + } + + /** + * @param methodName not used but could be + */ + static boolean isMethodNonqualified(String className, String methodName) { + if (className.equals("java.lang.Math")) { + switch (methodName) { + case "ulp": + case "nextDown": + case "nextUp": + case "nextAfter": + case "getExponent": + return false; + default: + return true; + } + } + return (isPackageOrClassNonqualified(className)); + } + + /** + * Check for special direct Clazz method calls, avoiding loading the entire + * class. + * + * @param javaClassName + * @return + */ + static String getJ2SFinalMapClazzMethod(String javaClassName, String methodName) { + switch (methodName) { + case "forName": + return (javaClassName.equals("java.lang.Class") ? "Clazz.forName" : null); + case "newInstance": + return (javaClassName.equals("java.lang.reflect.Array") ? "Clazz.array" : null); + default: + return null; + } + } + + /** + * Prepend a $ if the name is a JavaScript keyword such as "var" or "for". + * + * For example, + * + * int var = 3 + * + * becomes + * + * int $var = 3 + * + * @param identifier + * @param addName + * @param binding when null, this is for a field; when not null, this is for + * a method + * @return + */ + static String getJavaScriptCollisionIdentifier(String identifier, boolean addName) { + String s = getJavaScriptKeywordViolationChar(identifier); + return (!addName ? s : s.length() == 0 ? identifier : s + identifier); + } + + /* + * IE passes the following: public,private,private,static,package, + * implements,prototype,false,throws,label + * + * Firefox passes the following: public,prototype,false,label + * + * The following does not contain all the reserved keywords: + * http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference: + * Reserved_Words + * + * abstract, boolean, break, byte, case, catch, char, class, const, continue, + * debugger, default, delete, do, double, else, enum, export, extends, false, + * final, finally, float, for, function, goto, if, implements, import, in, + * instanceof, int, interface, long, native, new, null, package, private, + * private, public, return, short, static, super, switch, synchronized, this, + * throw, throws, transient, true, try, typeof, var, void, volatile, while, + * with, + * + * + * + */ + private static String[] keywords = new String[] { "class", /* "java", "javax", "sun", */"for", "while", "do", + "in", "return", "function", "var", "class", "public", "private", "new", "delete", "static", "package", + "import", "extends", "implements", "instanceof", "typeof", "void", "if", "this", "super", "prototype", + "else", "break", "true", "false", "try", "catch", "throw", "throws", "continue", "switch", "default", + "case", "export", "import", "const", /* "label", */"with", + // BH and a few of our own, based on checking developer console: + "c$", "apply", "arguments", "bind", "call", "caller", "watch", "unwatch", "valueOf", "isPrototypeOf", + "isGenerator", "prototype" }; + + private static String getJavaScriptKeywordViolationChar(String identifier) { + if (!Character.isUpperCase(identifier.charAt(0))) { + String s = identifier.intern(); + for (int i = 0; i < keywords.length; i++) { + if (keywords[i] == s) { + return "$"; + } + } + } + return ""; + } + + /** + * Prefix field name with as many $ as necessary to make it inheritance-unique + * + * @param binding + * @param name + * @return + */ + static StringBuffer getJ2S$$InheritedFieldName(ITypeBinding binding, String name, StringBuffer buf) { + if (binding != null) { + ITypeBinding superclass = binding.getSuperclass(); + if (superclass != null) { + IVariableBinding[] declaredFields = superclass.getDeclaredFields(); + for (int i = 0; i < declaredFields.length; i++) { + if (name.equals(declaredFields[i].getName())) { + buf.append("$"); + break; + } + } + return getJ2S$$InheritedFieldName(superclass, name, buf); + } + } + return buf.append(name); + } + + /** + * Initializer for the $$$... buffer that is prepended to all field names that + * have conflicts with methods. Currently just toString(); + * + * @param fieldName + * @param buf + * @return + */ + static StringBuffer newFieldNameBuf(String fieldName, StringBuffer buf) { + if (buf == null) + buf = new StringBuffer(); + if (fieldNameCoversMethod(fieldName)) + buf.append("$"); + return buf; + } + + /** + * toString is the only method not qualified. It will be used directly by + * JavaScript in s1 + s2. So in this case, we need a field by that name to be + * prepended with "$". + * + * @param fieldName + * @return + */ + static boolean fieldNameCoversMethod(String fieldName) { + return fieldName.equals("toString"); + } + + } + + static class ClassAnnotation { + + protected ASTNode node; + String qname; + protected Annotation annotation; + + protected ClassAnnotation(String qname, Annotation annotation, ASTNode node) { + this.annotation = annotation; + this.qname = qname; + this.node = node; + } + + @SuppressWarnings({ "unchecked" }) + public static void addClassAnnotations(Java2ScriptVisitor visitor, int accessType, List class_annotations, + List enums, List fields, List methods, + List innerClasses, TrailingBuffer trailingBuffer) { + boolean isPackage = (fields == null && enums == null); + int nn = 0, ptBuf = 0, ptBuf1 = 0; + ASTNode lastNode = null; + List fragments = null; + String propOrder = null; + String lastClassName = null; + String signature = null; + for (int i = 0; i < class_annotations.size(); i++) { + ClassAnnotation a = class_annotations.get(i); + String str = a.annotation.toString(); + IAnnotationBinding b = a.annotation.resolveAnnotationBinding(); + if (b != null && global_j2sFlag_isDebugging) + System.out.println("annotation " + str); + if (a.annotation instanceof NormalAnnotation) { + // @XmlElement(name="test",type=Integer.class) + // remove commas, add quotes + NormalAnnotation na = (NormalAnnotation) a.annotation; + IMemberValuePairBinding[] pairs = na.resolveAnnotationBinding().getDeclaredMemberValuePairs(); + str = str.substring(0, str.indexOf("(") + 1); + for (int j = 0; j < pairs.length; j++) + str += annotationNameValue(pairs[j].getName(), pairs[j].getValue()); + str += ")"; + } else if (a.annotation instanceof SingleMemberAnnotation) { + // add quotes + List expressions = null; + Expression e = ((SingleMemberAnnotation) a.annotation).getValue(); + if (e instanceof TypeLiteral) { + expressions = new ArrayList(); + expressions.add(e); + } else if (e instanceof ArrayInitializer) { + expressions = ((ArrayInitializer) e).expressions(); + } + if (expressions != null) { + str = str.substring(0, str.indexOf("(") + 1); + int n = expressions.size(); + String sep = (n > 1 ? "{" : ""); + for (int j = 0; j < n; j++) { + str += sep + annotationNameValue(null, expressions.get(j)); + sep = ","; + } + str += (n > 1 ? "})" : ")"); + } + } + if (a.node == lastNode) { + trailingBuffer.append(","); + trailingBuffer.insert(ptBuf1, "','" + a.qname); + } else { + lastNode = a.node; + String varName = null; + ITypeBinding type = null; + // time to pick up the fragments + addTrailingFragments(fragments, trailingBuffer, ptBuf); + fragments = null; + if (a.node instanceof EnumDeclaration) { + type = ((EnumDeclaration) a.node).resolveBinding(); + propOrder = "XX"; + } else if (a.node instanceof TypeDeclaration) { + type = ((TypeDeclaration) a.node).resolveBinding(); + } else if (a.node instanceof FieldDeclaration) { + FieldDeclaration field = (FieldDeclaration) a.node; + if (fields != null) + fields.remove(field); + fragments = field.fragments(); + VariableDeclarationFragment identifier = (VariableDeclarationFragment) fragments.get(0); + IVariableBinding var = identifier.resolveBinding(); + varName = var.getName(); + type = var.getType(); + } else if (a.node instanceof MethodDeclaration) { + MethodDeclaration method = (MethodDeclaration) a.node; + IMethodBinding mBinding = method.resolveBinding(); + if (methods != null) + if (methods.contains(mBinding)) + methods.remove(mBinding); + if (accessType != NOT_JAXB) + mBinding = getJAXBGetMethod(mBinding, methods, false); + if (mBinding == null) + continue; + varName = "M:" + mBinding.getName(); + signature = visitor.getFinalMethodNameOrArrayForDeclaration(mBinding, mBinding.isConstructor(), METHOD_FULLY_QUALIFIED); + type = mBinding.getReturnType(); + } else if (a.node instanceof AnnotationTypeMemberDeclaration) { + MethodDeclaration method = (MethodDeclaration) a.node; + IMethodBinding var = method.resolveBinding(); + if (methods != null) + if (methods.contains(var)) + methods.remove(var); +// if (accessType != NOT_JAXB) +// var = getJAXBGetMethod(var, methods, false); +// if (var == null) +// continue; + varName = "M:" + var.getName(); + type = var.getReturnType(); + } else if (a.node instanceof EnumConstantDeclaration) { + EnumConstantDeclaration con = (EnumConstantDeclaration) a.node; + if (enums != null) + enums.remove(con); + IVariableBinding var = con.resolveVariable(); + varName = var.getName(); + type = var.getType(); + } + String className = (type == null ? null + : type.isTypeVariable() ? type.toString() // could be "" + : j2sNonPrimitiveName(type, false)); + //stripJavaLang(NameMapper.fixPackageName(getJavaClassNameQualified(type)) + //String typeref = (type == null ? null : j2sClassObject(type)); + if (className != null && className.equals(lastClassName)) { + className = "."; + //typeref = "'.'"; + } else { + lastClassName = className; + } + trailingBuffer.append(nn++ == 0 ? "C$.$getAnn$ = function(){ return [\n[[" : "]],\n [["); + trailingBuffer.append((varName == null ? null : "'" + varName + "'")); + ptBuf = trailingBuffer.buf.length(); + trailingBuffer.append(",'" + className + "'," + //+ typeref + "," + + signature + ",['" + a.qname + + "']],["); + ptBuf1 = trailingBuffer.buf.length() - 5; + } + str = str.replace("'", "\\'"); + int pt = str.indexOf("("); + str = (pt >= 0 ? str.substring(pt+1, str.length() - 1) : ""); + trailingBuffer.append("'" + str + "'"); + if (propOrder == null && str.indexOf("propOrder=") >= 0) + propOrder = str; + } + if (nn > 0) { + addTrailingFragments(fragments, trailingBuffer, ptBuf); + if (!isPackage && accessType != NOT_JAXB) + addImplicitJAXBFieldsAndMethods(accessType, trailingBuffer, enums, fields, methods, innerClasses, + propOrder); + trailingBuffer.append("]]]}\n"); + } + if (global_j2sFlag_isDebugging) + System.out.println("pt=" + nn + " " + trailingBuffer); + } + + private static String annotationNameValue(String name, Object value) { + String str = (name == null ? "" : name + "="); + if (value instanceof TypeLiteral) { + str += "\"" + ((TypeLiteral) value).getType().resolveBinding().getQualifiedName() + ".class\""; + } else if (value instanceof IVariableBinding) { + str += "\"" + ((IVariableBinding) value).getType().getQualifiedName() + "." + + ((IVariableBinding) value).getName() + "\""; + } else if (value instanceof Expression) { + str += "\"" + value + "\""; + } else if (value instanceof ITypeBinding) { + str += "\"" + ((ITypeBinding) value).getQualifiedName() + ".class\""; + } else if (value instanceof Object[]) { + // propOrder + Object[] o = (Object[]) value; + str += "{"; + for (int i = 0; i < o.length; i++) + str += annotationNameValue(null, o[i]) + " "; + str += "}"; + } else { + str += "\"" + value.toString() + "\""; + } + str += " "; + return str; + } + + private static IMethodBinding getJAXBGetMethod(IMethodBinding var, List methods, + boolean returnVar2) { + String varName = var.getName(); + // check for matching get/is and set + if (varName.startsWith("create")) { + return var; + } + if (varName.startsWith("set")) { + return getMethodBinding(methods, "g" + varName.substring(1)); + } + IMethodBinding var2 = getMethodBinding(methods, + "set" + varName.substring(varName.startsWith("get") ? 3 : 2)); + return (var2 == null ? null : returnVar2 ? var2 : var); + } + + /** + * Add all implicit fields. Note that we still cannot marshal a class that has + * NO JAXB annotations at all. We have to have some. + * + * @param accessType + * @param trailingBuffer + * @param enums + * @param fields + * @param methods + * @param innerClasses + * @param propOrder + */ + private static void addImplicitJAXBFieldsAndMethods(int accessType, TrailingBuffer trailingBuffer, + List enums, List fields, List methods, + List innerClasses, String propOrder) { + for (int i = 0; i < innerClasses.size(); i++) { + ITypeBinding type = innerClasses.get(i).resolveBinding(); + if (isStatic(type)) { + addAnnotation(null, type, "!XmlInner", trailingBuffer); + } + } + + switch (accessType) { + case JAXB_TYPE_NONE: + return; + case JAXB_TYPE_ENUM: + for (int j = 0; j < enums.size(); j++) { + EnumConstantDeclaration con = enums.get(j); + IVariableBinding var = con.resolveVariable(); + String varName = var.getName(); + ITypeBinding type = var.getType(); + addAnnotation(varName, type, "@XmlEnumValue", trailingBuffer); + } + return; + default: + boolean isUnspecified = (accessType == JAXB_TYPE_UNSPECIFIED); + boolean publicOnly = (accessType == JAXB_TYPE_PUBLIC_MEMBER); + if (accessType != JAXB_TYPE_PROPERTY) { + for (int j = 0; j < fields.size(); j++) { + FieldDeclaration field = fields.get(j); + boolean isPublic = Modifier.isPublic(field.getModifiers()); + if (publicOnly && !isPublic) + continue; + List fragments = field.fragments(); + for (int i = 0; i < fragments.size(); i++) { + VariableDeclarationFragment identifier = (VariableDeclarationFragment) fragments.get(i); + IVariableBinding var = identifier.resolveBinding(); + String varName = var.getName(); + // If propOrder is defined, then we are only allowed to + // add implicit fields that are in that propOrder + if (propOrder != null && propOrder.indexOf("\"" + varName + "\"") < 0) + continue; + ITypeBinding type = var.getType(); + addAnnotation(varName, type, "@XmlElement", trailingBuffer); + if (isUnspecified) + addAnnotation(varName, type, "!XmlPublic(" + isPublic + ")", trailingBuffer); + } + } + } + if (accessType != JAXB_TYPE_FIELD) { + for (int i = 0; i < methods.size(); i++) { + IMethodBinding var = methods.get(i); + IMethodBinding var2 = getJAXBGetMethod(var, methods, true); + if (var2 == null) + continue; + boolean isPublic = (Modifier.isPublic(var.getModifiers()) + || Modifier.isPublic(var2.getModifiers())); + if (publicOnly && !isPublic) + continue; + String varName = var.getName(); + if (varName.startsWith("set")) + varName = (var = var2).getName(); + ITypeBinding type = var.getReturnType(); + addAnnotation("M:" + varName, type, "@XmlElement", trailingBuffer); + if (isUnspecified) + addAnnotation("M:" + varName, type, "!XmlPublic(" + isPublic + ")", trailingBuffer); + } + } + break; + } + } + + private static void addAnnotation(String varName, ITypeBinding type, String str, + TrailingBuffer trailingBuffer) { + String className = (stripJavaLang(NameMapper.fixPackageName(getJavaClassNameQualified(type)))); + trailingBuffer.append("]],\n [["); + trailingBuffer.append("'" + varName + "'"); + trailingBuffer.append(",'" + className + "'],['" + str + "'"); + } + + private static IMethodBinding getMethodBinding(List methods, String name) { + for (int i = methods.size(); --i >= 0;) { + if (name.equals(methods.get(i).getName())) { + return methods.remove(i); + } + } + return null; + } + + private static void addTrailingFragments(List fragments, TrailingBuffer trailingBuffer, int ptBuf) { + if (fragments == null || fragments.size() == 0) + return; + String line = trailingBuffer.buf.substring(ptBuf); + for (int f = 1; f < fragments.size(); f++) { + VariableDeclarationFragment identifier = (VariableDeclarationFragment) fragments.get(f); + IVariableBinding var = identifier.resolveBinding(); + trailingBuffer.append("]],\n [['" + var.getName() + "'"); + trailingBuffer.append(line); + } + } + + } + + static class NativeDoc { + + /** + * prepare a list that alternates [javadoc element javadoc element ... ] + * associating an element with its javadoc. + * + * @author RM + * + */ + private static class BlockVisitor extends ASTVisitor { + + private int ptrDoc0; + private List list; + private int listPtr; + + BlockVisitor(List list) { + this.list = list; + ptrDoc0 = list.get(listPtr = 0).getStartPosition(); + } + + /** + * Just collect blocks after j2s Javadocs. Throws an IndexOfBoundsException when + * done with scanning. + * + * Note that this no longer requires that the node be a block, because we are + * processing these BEFORE the node is visited. + * + */ + + public void preVisit(ASTNode node) throws IndexOutOfBoundsException { + checkNode(node, false); + } + + public void postVisit(ASTNode node) { + checkNode(node, true); + } + + private void checkNode(ASTNode node, boolean isPost) { + int nodept = node.getStartPosition() + (isPost ? node.getLength() : 0); + boolean checkParens = (!isPost && node instanceof ParenthesizedExpression); + while (nodept >= ptrDoc0 || checkParens && nodept == ptrDoc0 - 1) + addNode(node, isPost); + } + + private void addNode(ASTNode node, boolean isPost) { + if (isPost) + list.add(++listPtr, null); + list.add(++listPtr, node); + ptrDoc0 = list.get(++listPtr).getStartPosition(); + } + + } + + /** + * + * Check for j2sIgnore, j2sDebug, j2sNative + * + * @param javadoc + * @param isBlock + * @return true if code was added + */ + static boolean addJ2sJavadocs(StringBuffer buffer, List list, boolean isBlock) { + boolean didAdd = false; + int n = list.size(); + for (int i = 0; i < n; i++) { + Javadoc javadoc = list.get(i); + List tags = javadoc.tags(); + if (tags != null && tags.size() > 0 + && (isBlock && getTag(tags, "@j2sIgnore") != null + && addJ2SSourceForTag(buffer, null, i == 0, i == n - 1, true) + || isBlock && global_j2sFlag_isDebugging + && addJ2SSourceForTag(buffer, getTag(tags, "@j2sDebug"), i == 0, i == n - 1, + false) + || addJ2SSourceForTag(buffer, getTag(tags, "@j2sNative"), isBlock && i == 0, + isBlock && i == n - 1, false))) { + didAdd = true; + } + } + return didAdd; + } + + private static TagElement getTag(List tags, String j2sKey) { + Iterator iter = tags.iterator(); + while (iter.hasNext()) { + TagElement tagEl = (TagElement) iter.next(); + if (j2sKey.equals(tagEl.getTagName())) { + return tagEl; + } + } + return null; + } + + /** + * + * @param buffer + * @param tag + * @param addPrefix + * @param addPostfix + * @param isIgnore + * @return true to indicate we have written, so this block can be skipped + */ + private static boolean addJ2SSourceForTag(StringBuffer buffer, TagElement tag, boolean addPrefix, + boolean addPostfix, boolean isIgnore) { + if (isIgnore) { + buffer.append("\n{}\n"); + return true; + } + if (tag == null) + return false; + StringBuffer buf = new StringBuffer(); + List fragments = tag.fragments(); + for (Iterator iterator = fragments.iterator(); iterator.hasNext();) { + TextElement commentEl = (TextElement) iterator.next(); + String text = commentEl.getText().trim(); + buf.append(text); + if (text.length() != 0) { + buf.append(text.endsWith(";") || text.indexOf("//") >= 0 ? "\n" : " "); + // BH note that all line terminators are removed, + // as this causes problems after source cleaning, which may result + // in code such as: + // + // return + // x + // + // but this still does not fix the problem that we can have + // x = " + // " + // after source cleaning + } + } + String code = buf.toString(); + // /-* comment *-/ becomes /* comment */ and <@> becomes @ + if (code.length() > 0) + code = Pattern.compile("\\/-\\*(.*)\\*-\\/", Pattern.MULTILINE | Pattern.DOTALL).matcher(code) + .replaceAll("/*$1*/").replaceAll("<@>", "@").trim(); + // use of inline comment + // that is, no {...} after it, in the middle of an expression + // for example: int x = /**@j2sNative 32||*/15; + // has limitations in that you cannot replace a string by "" or an object by + // null. + // end with || to replace Java value; must not be 0, "", or null + // end with 0 && to replace a number with 0. + // end with null && to replace anything with null + // end with 1?xxx: to replace anything with xxx + // /** @j2sNatve ! */true + // (/** @j2sNative 1?x: */y) + // /** @j2sNative true || */() + + boolean isInline = code.endsWith("|") || code.endsWith("&") || code.endsWith(":") || code.endsWith("!"); + buffer.append(isInline ? "" : addPrefix ? "{\n" : "\n"); + buffer.append(code); + buffer.append(isInline ? "" : addPostfix ? "\n}\n" : "\n"); + return true; + } + + } + + /** + * for debugging -- to System.err.println + * + * @param msg + */ + public static void dumpStack(String msg) { + try { + throw new NullPointerException("Why am I here? " + msg); + } catch (Exception e) { + e.printStackTrace(System.out); + } + } + + public static String j2sClassObject(ITypeBinding type) { + boolean isArray = type.isArray() && !type.isTypeVariable(); + if (isArray) + type = type.getComponentType(); + String name = (type.isPrimitive() ? type.getName() : j2sNonPrimitiveName(type, true)); + return "'" + (isArray ? name + "[]" : name) + "'"; + } + +// private static Map nonPrimitiveJ2STypeNames = new Hashtable<>(); + + static String j2sNonPrimitiveName(ITypeBinding type, boolean typeAsObject) { + if (type.isTypeVariable()) { + String n = type.toString(); + if (n.startsWith("<")) { + return (typeAsObject ? "Object" : n); + } + } + return stripJavaLang(removeBracketsAndFixNullPackageName(getJavaClassNameQualified(type))); + } + + +//////////////////JAVA 8 LAMBDA ADDITIONS ///////////////// + + /** + * + * Function iaCreator = int[]::new; + * + */ + public boolean visit(CreationReference node) { +// // lambda_C + ITypeBinding binding = node.resolveTypeBinding(); + processLocalInstance(node, null, binding, null, null, LAMBDA_CREATION, LAMBDA_UNWRAPPED); + return false; + } + + /** + * s -> b(s) + * + */ + public boolean visit(LambdaExpression node) { + // LambdaExpression: + // Identifier -> Body + // ( [ Identifier { , Identifier } ] ) -> Body + // ( [ FormalParameter { , FormalParameter } ] ) -> Body + // ==> + // new runnable() { public xxx singleMethod() { Body }) + + // lambda_E + int pt = buffer.length(); + int localType = class_localType; + class_localType = LAMBDA_EXPRESSION; + String anonName = processLocalInstance(node, null, node.resolveTypeBinding(), null, null, LAMBDA_EXPRESSION, LAMBDA_EXPRESSION); + class_localType = localType; + if (anonName != null) + addLambdaReuse(pt, anonName); + return false; + } + + /** + * System.out::println; + * + * new Test()::test2; + * + */ + public boolean visit(ExpressionMethodReference node) { + // lambda_M + // oddly enough, if we just use visit(MethodReference) it goes + // somewhere else. + return addLambdaMethodReference(node, node.getExpression()); + } + + /** + * + * super::test2 + * + */ + public boolean visit(SuperMethodReference node) { + return addLambdaMethodReference(node, null); + } + + /** + * System.out::println + */ + public boolean visit(TypeMethodReference node) { + return addLambdaMethodReference(node, null); + } + + /** + * Class::method + * + * super::method + * + * @param node + * @param exp + * @return false + */ + private boolean addLambdaMethodReference(MethodReference node, Expression exp) { + ITypeBinding binding = node.resolveTypeBinding(); + IMethodBinding mBinding = node.resolveMethodBinding(); + ITypeBinding declaringClass = mBinding.getDeclaringClass(); + int pt = buffer.length(); + buffer.append("(function($$){"); + int localType = class_localType; + class_localType = LAMBDA_WRAPPED; + // for the method invocation visitor + String anonName = processLocalInstance(node, null, binding, null, null, LAMBDA_METHOD, LAMBDA_WRAPPED); + class_localType = localType; + buffer.append("})("); + appendFinalMethodQualifier(exp, declaringClass, null, FINAL_ESCAPECACHE | FINAL_LAMBDA); + buffer.append(")"); + if (anonName != null) + addLambdaReuse(pt, anonName); + return false; + } + + /** + * allow reuse of Lambda method and expression objects when they are named + * + * @param pt + * @param anonName + */ + private void addLambdaReuse(int pt, String anonName) { + String tmp = buffer.substring(pt); + buffer.setLength(pt); + anonName = getFinalJ2SClassName(anonName, FINAL_P); + buffer.append("(" + anonName + "$||(" + anonName + "$=(") + .append(tmp).append(")))"); + } + + private void addLambdaBody(ASTNode body) { + if (body instanceof Block) { + body.accept(this); + } else { + // there may be no return, but we still want to do this + buffer.append("{ return "); + if (body == null) + return; // handled elsewhere + buffer.append("("); + body.accept(this); + buffer.append(");}"); + } + } + + private String getLambdaParamList(IMethodBinding mBinding, int arity0) { + int n = mBinding.getParameterTypes().length; + if (arity0 < 0) + arity0 = n; + // accept(t,u)......add(u) + return (n == 0 ? "" : " t,u,v,w,x,y,z".substring(Math.max(0, (arity0 - n) * 2) + 1, arity0 * 2)); + } + + /** + * Create the lambda class + * @param lnode + * @param mBinding + */ + private boolean addLambdaClass(ASTNode lnode, IMethodBinding mBinding) { + if (lnode instanceof LambdaExpression) { + buffer.append("/*lambda_E*/"); + LambdaExpression node = (LambdaExpression) lnode; + mBinding = node.resolveMethodBinding(); + @SuppressWarnings("unchecked") + List params = node.parameters(); + int localType = class_localType; + class_localType = LAMBDA_EXPRESSION; + processMethodDeclaration(mBinding, params, node.getBody(), false, LAMBDA_EXPRESSION); + class_localType = localType; + return true; + } + if (lnode instanceof CreationReference) { + buffer.append("/*lambda_C*/"); + processMethodDeclaration(mBinding, null, null, false, LAMBDA_CREATION); + CreationReference node = (CreationReference) lnode; + Type ctype = node.getType(); + ITypeBinding binding = ctype.resolveBinding(); + if (ctype instanceof ArrayType) { + // int[]::new; + addArrayConstructor(binding, null); + } else { + // MatchSink::new; + addConstructor(binding, mBinding, null, mBinding.getParameterTypes().length); + } + buffer.append("});\n"); + return true; + } + // method of one type or another + SimpleName identifier; + Expression exp = null; + IMethodBinding mBinding1; + if (lnode instanceof TypeMethodReference) { + // Collection::add (see java.stream.Collectors) + buffer.append("/*lambda_T*/"); + TypeMethodReference node = (TypeMethodReference) lnode; + identifier = node.getName(); + mBinding1 = node.resolveMethodBinding(); + } else if (lnode instanceof SuperMethodReference) { + // super::test3 + buffer.append("/*lambda_S*/"); + SuperMethodReference node = (SuperMethodReference) lnode; + identifier = node.getName(); + mBinding1 = node.resolveMethodBinding(); + } else if (lnode instanceof ExpressionMethodReference) { + // String.out::println + buffer.append("/*lambda_M*/"); + ExpressionMethodReference node = (ExpressionMethodReference) lnode; + identifier = node.getName(); + mBinding1 = node.resolveMethodBinding(); + exp = node.getExpression(); + } else { + buffer.append("/*lambda_?*/"); + log("??? addLambdaMethod " + lnode.getClass().getName()); + return false; + } + processMethodDeclaration(mBinding, null, null, false, LAMBDA_METHOD); + boolean isStatic = addMethodInvocation(identifier, null, mBinding1, exp, mBinding.getParameterTypes().length); + buffer.append("});\n"); + return isStatic; + } + + /** + * SwingJS uses var p$ within an anonymous function wrapper ;(function() + * {.....})(); to isolate local variables within all classes of all types. + * Within this framework, C$ is the raw JavaScript object for the class, p$ + * is is an associative array containing private methods. + * + * This hashtable is reset for each top-level class, indicating which private + * var to use for a private method -- p$1, p$2, p$3 etc. -- depending upon the + * class being referred to. + * + */ + private static Map classToPrivateVar = new Hashtable(); + private static String privateVarString = ""; + private static int privateClassCount = 0; + private static int privateVarCount = 0; + + /** + * p$1, p$2, etc. + * + * Also used to compare two classes for equivalence, because I cannot figure out + * how to equate a class being invoked and a class being declared + * + * @param binding + * @return + */ + private String getPrivateVar(IBinding binding, boolean isClassCompare) { + String key = binding.getKey(), key0 = null, key1 = null; + if (isClassCompare) + key = "_" + key; + String p$ = classToPrivateVar.get(key); + if (p$ == null) { + key0 = key; + p$ = classToPrivateVar.get(key = (isClassCompare ? "_" : "") + getNormalizedKey(binding)); + } + if (p$ == null && !isClassCompare && key.indexOf("[") >= 0) { + key1 = key; + p$ = classToPrivateVar.get(key = key.substring(0, key.indexOf("[") + 1) + "]"); + } + if (p$ == null) { + classToPrivateVar.put(key, p$ = "p$" + (isClassCompare ? ++privateClassCount : ++privateVarCount)); + classToPrivateVar.put(key0, p$); + if (!isClassCompare) { + if (key1 != null) + classToPrivateVar.put(key1, p$); + privateVarString += "," + p$ + "={}"; + } + } + return p$; + } + + /** + * Test to see if an invocation is the same as a declaration using cached + * normalized keys + * + * @param a + * @param b + * @return + */ + private boolean areEqual(IBinding a, IBinding b) { + return getPrivateVar(a, true).equals(getPrivateVar(b, true)); + } + + private static String getNormalizedKey(IBinding b) { + return getNormalizedSubKey(b.getKey()); + // e.g.: invocation vs. declaration + // Ltest/Test_GenericIMV_AB; + // becomes test/Test_GenericIMV_AB[TA;TB;]; + // Ljava/util/Hashtable.Enumerator; + // becomes java/util/Hashtable[TK;TV;].Enumerator[TT;]; + } + + private static String getNormalizedSubKey(String key) { + int pt0 = key.lastIndexOf("<"), pt1; + if (pt0 >= 0) + return getNormalizedSubKey(key.substring(0, pt0) + "[" + getNormalizedSubKey( + key.substring(pt0 + 1, (pt1 = key.indexOf(">", pt0))) + "]" + key.substring(pt1 + 1))); + if (key.indexOf(":") < 0) + return key; + int left = -1; + int pt; + while ((pt = key.indexOf(":", ++left)) >= 0) { + // Ltest/Test_GenericIMV_AB;:TA;Ltest/Test_GenericIMV_AB;:TB; + // ^ ^ + // include ; but not : + key = key.substring(0, left) + key.substring(pt + 1); + // TA;Ltest/Test_GenericIMV_AB;:TB; + // ^ + left = key.indexOf(";", left); + if (left < 0) + break; + + // Ljava/util/Hashtable.Enumerator; + // Ljava/util/Hashtable.Enumerator[TT;]; + // Ljava/util/Hashtable[TK;TV;].Enumerator[TT;]; + + } + return key; + } + + private void resetPrivateVars() { + privateVarCount = privateClassCount = 0; + privateVarString = ""; + classToPrivateVar.clear(); + } + + ///////////////// debugging ////////////////////////// + + /** + * Add a comment message in the output buffer for debugging. + * + * @param msg + */ + void bufferDebug(String msg) { + buffer.append("/*" +msg + "*/"); + } + +// void debugDumpClass(ITypeBinding binding) { +// ITypeBinding[] lst = binding.getTypeParameters(); +// +// // Check for - these are for the generic class defs themselves +// for (int i = 0; i < lst.length; i++) +// log(binding.getKey() + "typeP " + i + lst[i].getName()); +// +// // check for for the implemented classes +// lst = binding.getTypeArguments(); +// for (int i = 0; i < lst.length; i++) +// log(binding.getKey() + "typeA " + i + lst[i].getName()); +// +// IMethodBinding[] methods = binding.getDeclaredMethods(); +// for (int i = methods.length; --i >= 0;) { +// IMethodBinding m = methods[i]; +// log(getFinalMethodNameWith$Params(m.getName(), null, m, null, false, METHOD_NOTSPECIAL)); +// ITypeBinding[] params = m.getParameterTypes(); +// for (int j = 0; j < params.length; j++) +// log("\t" + params[j].getName()); +// +// } +// } +// +// static void debugListAllOverrides(ITypeBinding binding) { +// IMethodBinding[] jmethods = binding.getDeclaredMethods(); +// for (int j = jmethods.length; --j >= 0;) { +// IMethodBinding m = jmethods[j]; +// ITypeBinding b = null; +// while ((b = (b == null ? m.getDeclaringClass() : b.getSuperclass())) != null) { +// IMethodBinding[] methods = b.getDeclaredMethods(); +// for (int i = methods.length; --i >= 0;) +// if (m.overrides(methods[i])) +// log("!! " + m.getKey() + " overrides " + methods[i].getKey()); +// } +// } +// } +// +} diff --git a/sources/net.sf.j2s.java.core/.j2s b/sources/net.sf.j2s.java.core/.j2s index 0acf828b6..79c85caef 100644 --- a/sources/net.sf.j2s.java.core/.j2s +++ b/sources/net.sf.j2s.java.core/.j2s @@ -1,5 +1,11 @@ j2s.compiler.status=enable +# read.annotations to false to not record annotations +j2s.compiler.read.annotations=true + +# customizable list for what annotations to skip +j2s.compiler.ignored.annotations=CallerSensitive;ConstructorProperties;Deprecated;Override;SaveVarargs;SuppressWarnings; + #j2s.compiler.nonqualified.packages=org.w3c.dom #output file name for logging methods declared - delete the file to regenerate a listing diff --git a/sources/net.sf.j2s.java.core/dist/SwingJS-site.zip b/sources/net.sf.j2s.java.core/dist/SwingJS-site.zip index d39f23180..01d6b0802 100644 Binary files a/sources/net.sf.j2s.java.core/dist/SwingJS-site.zip and b/sources/net.sf.j2s.java.core/dist/SwingJS-site.zip differ diff --git a/sources/net.sf.j2s.java.core/doc/Differences.txt b/sources/net.sf.j2s.java.core/doc/Differences.txt index e4fd4f383..06a0a6d6e 100644 --- a/sources/net.sf.j2s.java.core/doc/Differences.txt +++ b/sources/net.sf.j2s.java.core/doc/Differences.txt @@ -32,6 +32,7 @@ ever be overridden by subclasses. As in java.lang.Thread.java: ---------------------------------- +updated 12/22/19 -- additional issues updated 11/03/19 -- adds information about File.exists() and points to src/javajs/async updated 10/26/19 -- adds information about File.createTempFile() updated 8/16/19 -- minor typos and added summary paragraph @@ -70,6 +71,42 @@ that one can call in JavaScript and for all practical purposes it will appear that Java is running. +Method Disambiguation +--------------------- + +SwingJS has no problem with the overloading of methods, for example: + + public void print(int b); + public void print(float b); + +JavaScript does not allow overloading of methods, and the common practice in +Java of naming a field the same as a method -- isAllowed and isAllowed() -- is +not possible in JavaScript. As a result, SwingJS implements "fully-qualified" +method names using "$" parameter type separation. For example, in Java we see: + +public void equals(Object o) {...} + +Whereas in SwingJS we have: + +Clazz.newMeth(C$, 'equals$O', function (o) {...} + +And + + this.getContentPane().add(bar, "North"); + +becomes + + this.getContentPane$().add$java_awt_Component$O(bar, "North"); + + +Parameterless methods such as toString() are appended with "$" to become toString$(). +The one exception to this rule is private methods, which are saved in (truly) private +array in the class (and are not accessible by reflection). Private parameterless +methods retain their simple Java name, since they cannot conflict with field names. + +This renaming of methods has a few consequences, which are discussed more fully below. + + Applet vs. Application ---------------------- @@ -173,6 +210,7 @@ myclass.xxx in Java (and JavaScript). This was done extensively in the Jalview project. See jalview.bin.Instance. + Helper Packages -- swingjs/ and javajs/ --------------------------------------- @@ -201,8 +239,6 @@ See javajs.async.Async JavaDoc comments for a full description of these useful classes. - - Modal Dialogs ------------- @@ -296,6 +332,7 @@ a2s components, which in turn subclass JComponents. So no changes in code are ne successfully transpiled over 500 applets using this strategy. (Kind of surprising, actually, that the original Java developers did not see that option. But we have a hindsight advantage here.) + Working with Files ================== @@ -316,11 +353,15 @@ does not generate a pseudo-download to the user's machine. UNIMPLEMENTED CLASSES BY DESIGN =============================== +The SwingJS implementation of the following classes are present +in a way that gracefully bypasses their functionality: + accessibility security serialization + TODO LIST FOR UNIMPLEMENTED CLASSES =================================== @@ -340,7 +381,10 @@ Thread.currentThread() == dispatchThread MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS ===================================================================== +See below for a full discussion. +Names with "$" and "_" +positive integers do not add to give negative numbers ArrayIndexOutOfBounds java.awt.Color native methods @@ -361,7 +405,7 @@ MAJOR ISSUES--for Bob and Udo within SwingJS fonts OS-dependent classes AWT component peers - +some aspects of reflection MAJOR ISSUES--to be resolved by implementers ============================================ @@ -441,26 +485,24 @@ changed to JSToolkit.isDispatchThread() MINOR ISSUES--requiring some rewriting/refactoring outside of SwingJS ===================================================================== -primitive numerical types -------------------------- +positive integers do not add to give negative numbers +----------------------------------------------------- -Large integers will never roll over to negative ones. They will -just get bigger. - - int newLength = lineBuf.length * 2; - /** - * @j2sIgnore - */ - { - // never going to happen in JavaScript - if (newLength < 0) { - newLength = Integer.MAX_VALUE; - } - } +In Java, the following is true: + + 2000000000 + 2000000000 == -294967296 + +But in SwingJS, that will be 4000000000. So, for example, the following +strategy will fail in SwingJS: + + int newLength = lineBuf.length * 2; + if (newLength < 0) { + newLength = Integer.MAX_VALUE; + } +"-1" in JavaScript is not 0xFFFFFFFF. -Because "-1" in JavaScript is not 0xFFFFFFFF one must take care to not compare a negative -number with a 32-bit mask. So +And one must take care to not compare a negative number with a 32-bit mask. So (b & 0xFF000000) == 0xFF000000 @@ -475,6 +517,74 @@ The fix is that one must compare similar operations: if ((b & 0xFF000000) == (0xFF000000 & 0xFF000000)) ..... +Importantly, the JavaScript Int32Array does behave properly. From +the Firefox developer console: + +>> x = new Int32Array(1) +<- Int32Array(1) [ 0 ] +>> x[0] = 4000000000 +<- 4000000000 +>> x[0] +<- -294967296 + +Notice that, perhaps unexpectedly, the following two constructs produce +different results in JavaScript: + +x = new Int32Array(1); +b = x[0] = 4000000000; + +(b will be 4000000000) + +and + +x = new Int32Array(1); +x[0] = 4000000000; +b = x[0]; + +(b will be -294967296) + + +SwingJS leverages array typing to handle all byte and short arithmetic so as +to ensure that any byte or short operation in JavaScript does give the same +result in Java. The design decision to not also do this with integer math was +a trade-off between performance and handling edge cases. + + +Names with "$" and "_" +---------------------- + +For the most part, this should be no problem. + +Note that the use of $ and _ in Java field names has always been discouraged: +[https://docs.oracle.com/javase/tutorial/java/nutsandbolts/variables.html] + + You may find some situations where auto-generated names will contain the dollar sign, + but your variable names should always avoid using it. A similar convention + exists for the underscore character; while it's technically legal to begin your + variable's name with "_", this practice is discouraged. + +Some impacts of transpiling method names with full qualification: + +1) SwingJS will introduce fields that start with $ or _. These will not conflict + if the above convention is followed. + +2) Fields that have the same Java name as a method are not an issue. + +3) Fields that have a Java name with $ that matches a transpiled method name, + such as toString$, will need to be refactored in Java to not have that name collision. + +4) Fields in a subclass that have the same name as private fields in a superclass + represent a name collision, because the superclass method needs to call its private + field even if invoked from a subclass. The solution was to modify the subclass field + name using one or more prepended $. + +5) Use of Class.getDeclaredMethods() reflection will return Method objects having the transpiled + name, not the Java name. This could require some j2sNative adjustment + to strip the $... parameters from the name if that is needed. + +6) Use of Method.getParameterTypes() should work fine, provided class names + do not contain "_". This is because the transpiler converts "." to "_" when + creating the fully qualified JavaScript name. ArrayIndexOutOfBounds @@ -1026,6 +1136,7 @@ The JS document model does not allow two text fields to address the same underly Formatter/Regex limitations --------------------------- +Some browsers cannot process Regex "look-behind" process such as (?<=\W) java.util.regex.Matcher and Pattern use JavaScript's RegExp object rather than the native Java object. These are not identical. Only flags /igm are supported. Matcher.start(groupID) is not supported. @@ -1052,3 +1163,18 @@ may or may not be present with the JAR or class files. Use class files at your o Bob Hanson 2019.08.16 + + +Additional Issues +----------------- + +Annotation is working for classes, methods, and fields (12/22/19). Method reflection, however, +is limited. Interfaces do not expose their methods, as the transpiler does not actually transpile +the interfaces themselves. And method reflection only includes annotated methods. + +java.util.concurrent is not fully elaborated. This package is rewritten to not actually use the +memory handling capabilities of concurrency, which JavaScript does not have access to. + +System.getProperties() just returns a minimal set of properties. + + diff --git a/sources/net.sf.j2s.java.core/site-resources/test-raf.txt b/sources/net.sf.j2s.java.core/site-resources/test-raf.txt new file mode 100644 index 000000000..163504823 --- /dev/null +++ b/sources/net.sf.j2s.java.core/site-resources/test-raf.txt @@ -0,0 +1 @@ +this is a testing RandomAccessFile diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPCompressionTypes.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPCompressionTypes.java new file mode 100644 index 000000000..c61beeeb7 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPCompressionTypes.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +public class BMPCompressionTypes { + + private static final String[] compressionTypeNames = + {"BI_RGB", "BI_RLE8", "BI_RLE4", "BI_BITFIELDS", "BI_JPEG", "BI_PNG"}; + + static int getType(String typeString) { + for (int i = 0; i < compressionTypeNames.length; i++) + if (compressionTypeNames[i].equals(typeString)) + return i; + return 0; + } + + static String getName(int type) { + return compressionTypeNames[type]; + } + + public static String[] getCompressionTypes() { + return compressionTypeNames.clone(); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPConstants.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPConstants.java new file mode 100644 index 000000000..9f510c5ba --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPConstants.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +public interface BMPConstants { + // bmp versions + static final String VERSION_2 = "BMP v. 2.x"; + static final String VERSION_3 = "BMP v. 3.x"; + static final String VERSION_3_NT = "BMP v. 3.x NT"; + static final String VERSION_4 = "BMP v. 4.x"; + static final String VERSION_5 = "BMP v. 5.x"; + + // Color space types + static final int LCS_CALIBRATED_RGB = 0; + static final int LCS_sRGB = 1; + static final int LCS_WINDOWS_COLOR_SPACE = 2; + static final int PROFILE_LINKED = 3; + static final int PROFILE_EMBEDDED = 4; + + // Compression Types + static final int BI_RGB = 0; + static final int BI_RLE8 = 1; + static final int BI_RLE4 = 2; + static final int BI_BITFIELDS = 3; + static final int BI_JPEG = 4; + static final int BI_PNG = 5; +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageReader.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageReader.java new file mode 100644 index 000000000..1b750c1b9 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageReader.java @@ -0,0 +1,1860 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferUShort; +import java.awt.image.DirectColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.WritableRaster; + +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.event.IIOReadProgressListener; +import javax.imageio.event.IIOReadUpdateListener; +import javax.imageio.event.IIOReadWarningListener; + +import java.io.*; +import java.nio.*; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.StringTokenizer; + +import com.sun.imageio.plugins.common.ImageUtil; +import com.sun.imageio.plugins.common.I18N; + +/** This class is the Java Image IO plugin reader for BMP images. + * It may subsample the image, clip the image, select sub-bands, + * and shift the decoded image origin if the proper decoding parameter + * are set in the provided ImageReadParam. + * + * This class supports Microsoft Windows Bitmap Version 3-5, + * as well as OS/2 Bitmap Version 2.x (for single-image BMP file). + */ +public class BMPImageReader extends ImageReader implements BMPConstants { + // BMP Image types + private static final int VERSION_2_1_BIT = 0; + private static final int VERSION_2_4_BIT = 1; + private static final int VERSION_2_8_BIT = 2; + private static final int VERSION_2_24_BIT = 3; + + private static final int VERSION_3_1_BIT = 4; + private static final int VERSION_3_4_BIT = 5; + private static final int VERSION_3_8_BIT = 6; + private static final int VERSION_3_24_BIT = 7; + + private static final int VERSION_3_NT_16_BIT = 8; + private static final int VERSION_3_NT_32_BIT = 9; + + private static final int VERSION_4_1_BIT = 10; + private static final int VERSION_4_4_BIT = 11; + private static final int VERSION_4_8_BIT = 12; + private static final int VERSION_4_16_BIT = 13; + private static final int VERSION_4_24_BIT = 14; + private static final int VERSION_4_32_BIT = 15; + + private static final int VERSION_3_XP_EMBEDDED = 16; + private static final int VERSION_4_XP_EMBEDDED = 17; + private static final int VERSION_5_XP_EMBEDDED = 18; + + // BMP variables + private long bitmapFileSize; + private long bitmapOffset; + private long compression; + private long imageSize; + private byte palette[]; + private int imageType; + private int numBands; + private boolean isBottomUp; + private int bitsPerPixel; + private int redMask, greenMask, blueMask, alphaMask; + + private SampleModel sampleModel, originalSampleModel; + private ColorModel colorModel, originalColorModel; + + /** The input stream where reads from */ + private ImageInputStream iis = null; + + /** Indicates whether the header is read. */ + private boolean gotHeader = false; + + /** The original image width. */ + private int width; + + /** The original image height. */ + private int height; + + /** The destination region. */ + private Rectangle destinationRegion; + + /** The source region. */ + private Rectangle sourceRegion; + + /** The metadata from the stream. */ + private BMPMetadata metadata; + + /** The destination image. */ + private BufferedImage bi; + + /** Indicates whether subsampled, subregion is required, and offset is + * defined + */ + private boolean noTransform = true; + + /** Indicates whether subband is selected. */ + private boolean seleBand = false; + + /** The scaling factors. */ + private int scaleX, scaleY; + + /** source and destination bands. */ + private int[] sourceBands, destBands; + + /** Constructs BMPImageReader from the provided + * ImageReaderSpi. + */ + public BMPImageReader(ImageReaderSpi originator) { + super(originator); + } + + /** Overrides the method defined in the superclass. */ + public void setInput(Object input, + boolean seekForwardOnly, + boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + iis = (ImageInputStream) input; // Always works + if(iis != null) + iis.setByteOrder(ByteOrder.LITTLE_ENDIAN); + resetHeaderInfo(); + } + + /** Overrides the method defined in the superclass. */ + public int getNumImages(boolean allowSearch) throws IOException { + if (iis == null) { + throw new IllegalStateException(I18N.getString("GetNumImages0")); + } + if (seekForwardOnly && allowSearch) { + throw new IllegalStateException(I18N.getString("GetNumImages1")); + } + return 1; + } + + @Override + public int getWidth(int imageIndex) throws IOException { + checkIndex(imageIndex); + try { + readHeader(); + } catch (IllegalArgumentException e) { + throw new IIOException(I18N.getString("BMPImageReader6"), e); + } + return width; + } + + public int getHeight(int imageIndex) throws IOException { + checkIndex(imageIndex); + try { + readHeader(); + } catch (IllegalArgumentException e) { + throw new IIOException(I18N.getString("BMPImageReader6"), e); + } + return height; + } + + private void checkIndex(int imageIndex) { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException(I18N.getString("BMPImageReader0")); + } + } + + /** + * Process the image header. + * + * @exception IllegalStateException if source stream is not set. + * + * @exception IOException if image stream is corrupted. + * + * @exception IllegalArgumentException if the image stream does not contain + * a BMP image, or if a sample model instance to describe the + * image can not be created. + */ + protected void readHeader() throws IOException, IllegalArgumentException { + if (gotHeader) + return; + + if (iis == null) { + throw new IllegalStateException("Input source not set!"); + } + int profileData = 0, profileSize = 0; + + this.metadata = new BMPMetadata(); + iis.mark(); + + // read and check the magic marker + byte[] marker = new byte[2]; + iis.read(marker); + if (marker[0] != 0x42 || marker[1] != 0x4d) + throw new IllegalArgumentException(I18N.getString("BMPImageReader1")); + + // Read file size + bitmapFileSize = iis.readUnsignedInt(); + // skip the two reserved fields + iis.skipBytes(4); + + // Offset to the bitmap from the beginning + bitmapOffset = iis.readUnsignedInt(); + // End File Header + + // Start BitmapCoreHeader + long size = iis.readUnsignedInt(); + + if (size == 12) { + width = iis.readShort(); + height = iis.readShort(); + } else { + width = iis.readInt(); + height = iis.readInt(); + } + + metadata.width = width; + metadata.height = height; + + int planes = iis.readUnsignedShort(); + bitsPerPixel = iis.readUnsignedShort(); + + //metadata.colorPlane = planes; + metadata.bitsPerPixel = (short)bitsPerPixel; + + // As BMP always has 3 rgb bands, except for Version 5, + // which is bgra + numBands = 3; + + if (size == 12) { + // Windows 2.x and OS/2 1.x + metadata.bmpVersion = VERSION_2; + + // Classify the image type + if (bitsPerPixel == 1) { + imageType = VERSION_2_1_BIT; + } else if (bitsPerPixel == 4) { + imageType = VERSION_2_4_BIT; + } else if (bitsPerPixel == 8) { + imageType = VERSION_2_8_BIT; + } else if (bitsPerPixel == 24) { + imageType = VERSION_2_24_BIT; + } + + // Read in the palette + int numberOfEntries = (int)((bitmapOffset - 14 - size) / 3); + int sizeOfPalette = numberOfEntries*3; + palette = new byte[sizeOfPalette]; + iis.readFully(palette, 0, sizeOfPalette); + metadata.palette = palette; + metadata.paletteSize = numberOfEntries; + } else { + compression = iis.readUnsignedInt(); + imageSize = iis.readUnsignedInt(); + long xPelsPerMeter = iis.readInt(); + long yPelsPerMeter = iis.readInt(); + long colorsUsed = iis.readUnsignedInt(); + long colorsImportant = iis.readUnsignedInt(); + + metadata.compression = (int)compression; + metadata.xPixelsPerMeter = (int)xPelsPerMeter; + metadata.yPixelsPerMeter = (int)yPelsPerMeter; + metadata.colorsUsed = (int)colorsUsed; + metadata.colorsImportant = (int)colorsImportant; + + if (size == 40) { + // Windows 3.x and Windows NT + switch((int)compression) { + + case BI_JPEG: + case BI_PNG: + metadata.bmpVersion = VERSION_3; + imageType = VERSION_3_XP_EMBEDDED; + break; + + case BI_RGB: // No compression + case BI_RLE8: // 8-bit RLE compression + case BI_RLE4: // 4-bit RLE compression + + // Read in the palette + if (bitmapOffset < (size + 14)) { + throw new IIOException(I18N.getString("BMPImageReader7")); + } + int numberOfEntries = (int)((bitmapOffset-14-size) / 4); + int sizeOfPalette = numberOfEntries * 4; + palette = new byte[sizeOfPalette]; + iis.readFully(palette, 0, sizeOfPalette); + + metadata.palette = palette; + metadata.paletteSize = numberOfEntries; + + if (bitsPerPixel == 1) { + imageType = VERSION_3_1_BIT; + } else if (bitsPerPixel == 4) { + imageType = VERSION_3_4_BIT; + } else if (bitsPerPixel == 8) { + imageType = VERSION_3_8_BIT; + } else if (bitsPerPixel == 24) { + imageType = VERSION_3_24_BIT; + } else if (bitsPerPixel == 16) { + imageType = VERSION_3_NT_16_BIT; + + redMask = 0x7C00; + greenMask = 0x3E0; + blueMask = (1 << 5) - 1;// 0x1F; + metadata.redMask = redMask; + metadata.greenMask = greenMask; + metadata.blueMask = blueMask; + } else if (bitsPerPixel == 32) { + imageType = VERSION_3_NT_32_BIT; + redMask = 0x00FF0000; + greenMask = 0x0000FF00; + blueMask = 0x000000FF; + metadata.redMask = redMask; + metadata.greenMask = greenMask; + metadata.blueMask = blueMask; + } + + metadata.bmpVersion = VERSION_3; + break; + + case BI_BITFIELDS: + + if (bitsPerPixel == 16) { + imageType = VERSION_3_NT_16_BIT; + } else if (bitsPerPixel == 32) { + imageType = VERSION_3_NT_32_BIT; + } + + // BitsField encoding + redMask = (int)iis.readUnsignedInt(); + greenMask = (int)iis.readUnsignedInt(); + blueMask = (int)iis.readUnsignedInt(); + metadata.redMask = redMask; + metadata.greenMask = greenMask; + metadata.blueMask = blueMask; + + if (colorsUsed != 0) { + // there is a palette + sizeOfPalette = (int)colorsUsed*4; + palette = new byte[sizeOfPalette]; + iis.readFully(palette, 0, sizeOfPalette); + + metadata.palette = palette; + metadata.paletteSize = (int)colorsUsed; + } + metadata.bmpVersion = VERSION_3_NT; + + break; + default: + throw new + IIOException(I18N.getString("BMPImageReader2")); + } + } else if (size == 108 || size == 124) { + // Windows 4.x BMP + if (size == 108) + metadata.bmpVersion = VERSION_4; + else if (size == 124) + metadata.bmpVersion = VERSION_5; + + // rgb masks, valid only if comp is BI_BITFIELDS + redMask = (int)iis.readUnsignedInt(); + greenMask = (int)iis.readUnsignedInt(); + blueMask = (int)iis.readUnsignedInt(); + // Only supported for 32bpp BI_RGB argb + alphaMask = (int)iis.readUnsignedInt(); + long csType = iis.readUnsignedInt(); + int redX = iis.readInt(); + int redY = iis.readInt(); + int redZ = iis.readInt(); + int greenX = iis.readInt(); + int greenY = iis.readInt(); + int greenZ = iis.readInt(); + int blueX = iis.readInt(); + int blueY = iis.readInt(); + int blueZ = iis.readInt(); + long gammaRed = iis.readUnsignedInt(); + long gammaGreen = iis.readUnsignedInt(); + long gammaBlue = iis.readUnsignedInt(); + + if (size == 124) { + metadata.intent = iis.readInt(); + profileData = iis.readInt(); + profileSize = iis.readInt(); + iis.skipBytes(4); + } + + metadata.colorSpace = (int)csType; + + if (csType == LCS_CALIBRATED_RGB) { + // All the new fields are valid only for this case + metadata.redX = redX; + metadata.redY = redY; + metadata.redZ = redZ; + metadata.greenX = greenX; + metadata.greenY = greenY; + metadata.greenZ = greenZ; + metadata.blueX = blueX; + metadata.blueY = blueY; + metadata.blueZ = blueZ; + metadata.gammaRed = (int)gammaRed; + metadata.gammaGreen = (int)gammaGreen; + metadata.gammaBlue = (int)gammaBlue; + } + + // Read in the palette + int numberOfEntries = (int)((bitmapOffset-14-size) / 4); + int sizeOfPalette = numberOfEntries*4; + palette = new byte[sizeOfPalette]; + iis.readFully(palette, 0, sizeOfPalette); + metadata.palette = palette; + metadata.paletteSize = numberOfEntries; + + switch ((int)compression) { + case BI_JPEG: + case BI_PNG: + if (size == 108) { + imageType = VERSION_4_XP_EMBEDDED; + } else if (size == 124) { + imageType = VERSION_5_XP_EMBEDDED; + } + break; + default: + if (bitsPerPixel == 1) { + imageType = VERSION_4_1_BIT; + } else if (bitsPerPixel == 4) { + imageType = VERSION_4_4_BIT; + } else if (bitsPerPixel == 8) { + imageType = VERSION_4_8_BIT; + } else if (bitsPerPixel == 16) { + imageType = VERSION_4_16_BIT; + if ((int)compression == BI_RGB) { + redMask = 0x7C00; + greenMask = 0x3E0; + blueMask = 0x1F; + } + } else if (bitsPerPixel == 24) { + imageType = VERSION_4_24_BIT; + } else if (bitsPerPixel == 32) { + imageType = VERSION_4_32_BIT; + if ((int)compression == BI_RGB) { + redMask = 0x00FF0000; + greenMask = 0x0000FF00; + blueMask = 0x000000FF; + } + } + + metadata.redMask = redMask; + metadata.greenMask = greenMask; + metadata.blueMask = blueMask; + metadata.alphaMask = alphaMask; + } + } else { + throw new + IIOException(I18N.getString("BMPImageReader3")); + } + } + + if (height > 0) { + // bottom up image + isBottomUp = true; + } else { + // top down image + isBottomUp = false; + height = Math.abs(height); + } + + // Reset Image Layout so there's only one tile. + //Define the color space + ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); + if (metadata.colorSpace == PROFILE_LINKED || + metadata.colorSpace == PROFILE_EMBEDDED) { + + iis.mark(); + iis.skipBytes(profileData - size); + byte[] profile = new byte[profileSize]; + iis.readFully(profile, 0, profileSize); + iis.reset(); + + try { + if (metadata.colorSpace == PROFILE_LINKED && + isLinkedProfileAllowed() && + !isUncOrDevicePath(profile)) + { + String path = new String(profile, "windows-1252"); + + colorSpace = + new ICC_ColorSpace(ICC_Profile.getInstance(path)); + } else { + colorSpace = + new ICC_ColorSpace(ICC_Profile.getInstance(profile)); + } + } catch (Exception e) { + colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); + } + } + + if (bitsPerPixel == 0 || + compression == BI_JPEG || compression == BI_PNG ) + { + // the colorModel and sampleModel will be initialzed + // by the reader of embedded image + colorModel = null; + sampleModel = null; + } else if (bitsPerPixel == 1 || bitsPerPixel == 4 || bitsPerPixel == 8) { + // When number of bitsPerPixel is <= 8, we use IndexColorModel. + numBands = 1; + + if (bitsPerPixel == 8) { + int[] bandOffsets = new int[numBands]; + for (int i = 0; i < numBands; i++) { + bandOffsets[i] = numBands -1 -i; + } + sampleModel = + new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, + width, height, + numBands, + numBands * width, + bandOffsets); + } else { + // 1 and 4 bit pixels can be stored in a packed format. + sampleModel = + new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, + width, height, + bitsPerPixel); + } + + // Create IndexColorModel from the palette. + byte r[], g[], b[]; + if (imageType == VERSION_2_1_BIT || + imageType == VERSION_2_4_BIT || + imageType == VERSION_2_8_BIT) { + + + size = palette.length/3; + + if (size > 256) { + size = 256; + } + + int off; + r = new byte[(int)size]; + g = new byte[(int)size]; + b = new byte[(int)size]; + for (int i=0; i<(int)size; i++) { + off = 3 * i; + b[i] = palette[off]; + g[i] = palette[off+1]; + r[i] = palette[off+2]; + } + } else { + size = palette.length/4; + + if (size > 256) { + size = 256; + } + + int off; + r = new byte[(int)size]; + g = new byte[(int)size]; + b = new byte[(int)size]; + for (int i=0; i> 3; + srcOff[j] = 7 - (x & 7); + destPos[j] = i >> 3; + destOff[j] = 7 - (i & 7); + } + + int k = destinationRegion.y * lineStride; + if (isBottomUp) + k += (destinationRegion.height - 1) * lineStride; + + for (int j = 0, y = sourceRegion.y; + j < destinationRegion.height; j++, y+=scaleY) { + + if (abortRequested()) + break; + iis.read(buf, 0, lineLength); + for (int i = 0; i < destinationRegion.width; i++) { + //get the bit and assign to the data buffer of the raster + int v = (buf[srcPos[i]] >> srcOff[i]) & 1; + bdata[k + destPos[i]] |= v << destOff[i]; + } + + k += isBottomUp ? -lineStride : lineStride; + iis.skipBytes(skipLength); + processImageUpdate(bi, 0, j, + destinationRegion.width, 1, 1, 1, + new int[]{0}); + processImageProgress(100.0F*j/destinationRegion.height); + } + } + } + + // Method to read a 4 bit BMP image data + private void read4Bit(byte[] bdata) throws IOException { + + int bytesPerScanline = (width + 1) / 2; + + // Padding bytes at the end of each scanline + int padding = bytesPerScanline % 4; + if (padding != 0) + padding = 4 - padding; + + int lineLength = bytesPerScanline + padding; + + if (noTransform) { + int j = isBottomUp ? (height -1) * bytesPerScanline : 0; + + for (int i=0; i> 1; + srcOff[j] = (1 - (x & 1)) << 2; + destPos[j] = i >> 1; + destOff[j] = (1 - (i & 1)) << 2; + } + + int k = destinationRegion.y * lineStride; + if (isBottomUp) + k += (destinationRegion.height - 1) * lineStride; + + for (int j = 0, y = sourceRegion.y; + j < destinationRegion.height; j++, y+=scaleY) { + + if (abortRequested()) + break; + iis.read(buf, 0, lineLength); + for (int i = 0; i < destinationRegion.width; i++) { + //get the bit and assign to the data buffer of the raster + int v = (buf[srcPos[i]] >> srcOff[i]) & 0x0F; + bdata[k + destPos[i]] |= v << destOff[i]; + } + + k += isBottomUp ? -lineStride : lineStride; + iis.skipBytes(skipLength); + processImageUpdate(bi, 0, j, + destinationRegion.width, 1, 1, 1, + new int[]{0}); + processImageProgress(100.0F*j/destinationRegion.height); + } + } + } + + // Method to read 8 bit BMP image data + private void read8Bit(byte[] bdata) throws IOException { + + // Padding bytes at the end of each scanline + int padding = width % 4; + if (padding != 0) { + padding = 4 - padding; + } + + int lineLength = width + padding; + + if (noTransform) { + int j = isBottomUp ? (height -1) * width : 0; + + for (int i=0; i= sourceRegion.y && + lineNo < sourceRegion.y + sourceRegion.height) { + if (noTransform) { + int pos = lineNo * width; + for(int i = 0; i < width; i++) + bdata[pos++] = val[i]; + processImageUpdate(bi, 0, lineNo, + destinationRegion.width, 1, 1, 1, + new int[]{0}); + finished++; + } else if ((lineNo - sourceRegion.y) % scaleY == 0) { + int currentLine = (lineNo - sourceRegion.y) / scaleY + + destinationRegion.y; + int pos = currentLine * lineStride; + pos += destinationRegion.x; + for (int i = sourceRegion.x; + i < sourceRegion.x + sourceRegion.width; + i += scaleX) + bdata[pos++] = val[i]; + processImageUpdate(bi, 0, currentLine, + destinationRegion.width, 1, 1, 1, + new int[]{0}); + finished++; + } + } + processImageProgress(100.0F * finished / destinationRegion.height); + lineNo += isBottomUp ? -1 : 1; + l = 0; + + if (abortRequested()) { + flag = true; + } + + break; + + case 1: + // End-of-RLE marker + flag = true; + break; + + case 2: + // delta or vector marker + int xoff = values[count++] & 0xff; + int yoff = values[count] & 0xff; + // Move to the position xoff, yoff down + l += xoff + yoff*width; + break; + + default: + int end = values[count-1] & 0xff; + for (int i=0; i= sourceRegion.y && + lineNo < sourceRegion.y + sourceRegion.height) { + if (noTransform) { + int pos = lineNo * (width + 1 >> 1); + for(int i = 0, j = 0; i < width >> 1; i++) + bdata[pos++] = + (byte)((val[j++] << 4) | val[j++]); + if ((width & 1) == 1) + bdata[pos] |= val[width - 1] << 4; + + processImageUpdate(bi, 0, lineNo, + destinationRegion.width, 1, 1, 1, + new int[]{0}); + finished++; + } else if ((lineNo - sourceRegion.y) % scaleY == 0) { + int currentLine = (lineNo - sourceRegion.y) / scaleY + + destinationRegion.y; + int pos = currentLine * lineStride; + pos += destinationRegion.x >> 1; + int shift = (1 - (destinationRegion.x & 1)) << 2; + for (int i = sourceRegion.x; + i < sourceRegion.x + sourceRegion.width; + i += scaleX) { + bdata[pos] |= val[i] << shift; + shift += 4; + if (shift == 4) { + pos++; + } + shift &= 7; + } + processImageUpdate(bi, 0, currentLine, + destinationRegion.width, 1, 1, 1, + new int[]{0}); + finished++; + } + } + processImageProgress(100.0F * finished / destinationRegion.height); + lineNo += isBottomUp ? -1 : 1; + l = 0; + + if (abortRequested()) { + flag = true; + } + + break; + + case 1: + // End-of-RLE marker + flag = true; + break; + + case 2: + // delta or vector marker + int xoff = values[count++] & 0xFF; + int yoff = values[count] & 0xFF; + // Move to the position xoff, yoff down + l += xoff + yoff*width; + break; + + default: + int end = values[count-1] & 0xFF; + for (int i=0; i> 4 + : (values[count++] & 0x0f)); + } + + // When end is odd, the above for loop does not + // increment count, so do it now. + if ((end & 1) == 1) { + count++; + } + + // Whenever end pixels can fit into odd number of bytes, + // an extra padding byte will be present, so skip that. + if ((((int)Math.ceil(end/2)) & 1) ==1 ) { + count++; + } + break; + } + } else { + // Encoded mode + int alternate[] = { (values[count] & 0xf0) >> 4, + values[count] & 0x0f }; + for (int i=0; (i < value) && (l < width); i++) { + val[l++] = (byte)alternate[i & 1]; + } + + count++; + } + + // If End-of-RLE data, then exit the while loop + if (flag) { + break; + } + } + } + + /** Decodes the jpeg/png image embedded in the bitmap using any jpeg + * ImageIO-style plugin. + * + * @param bi The destination BufferedImage. + * @param bmpParam The ImageReadParam for decoding this + * BMP image. The parameters for subregion, band selection and + * subsampling are used in decoding the jpeg image. + */ + + private BufferedImage readEmbedded(int type, + BufferedImage bi, ImageReadParam bmpParam) + throws IOException { + String format; + switch(type) { + case BI_JPEG: + format = "JPEG"; + break; + case BI_PNG: + format = "PNG"; + break; + default: + throw new + IOException("Unexpected compression type: " + type); + } + ImageReader reader = + ImageIO.getImageReadersByFormatName(format).next(); + if (reader == null) { + throw new RuntimeException(I18N.getString("BMPImageReader4") + + " " + format); + } + // prepare input + byte[] buff = new byte[(int)imageSize]; + iis.read(buff); + reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(buff))); + if (bi == null) { + ImageTypeSpecifier embType = reader.getImageTypes(0).next(); + bi = embType.createBufferedImage(destinationRegion.x + + destinationRegion.width, + destinationRegion.y + + destinationRegion.height); + } + + reader.addIIOReadProgressListener(new EmbeddedProgressAdapter() { + public void imageProgress(ImageReader source, + float percentageDone) + { + processImageProgress(percentageDone); + } + }); + + reader.addIIOReadUpdateListener(new IIOReadUpdateListener() { + public void imageUpdate(ImageReader source, + BufferedImage theImage, + int minX, int minY, + int width, int height, + int periodX, int periodY, + int[] bands) + { + processImageUpdate(theImage, minX, minY, + width, height, + periodX, periodY, bands); + } + public void passComplete(ImageReader source, + BufferedImage theImage) + { + processPassComplete(theImage); + } + public void passStarted(ImageReader source, + BufferedImage theImage, + int pass, + int minPass, int maxPass, + int minX, int minY, + int periodX, int periodY, + int[] bands) + { + processPassStarted(theImage, pass, minPass, maxPass, + minX, minY, periodX, periodY, + bands); + } + public void thumbnailPassComplete(ImageReader source, + BufferedImage thumb) {} + public void thumbnailPassStarted(ImageReader source, + BufferedImage thumb, + int pass, + int minPass, int maxPass, + int minX, int minY, + int periodX, int periodY, + int[] bands) {} + public void thumbnailUpdate(ImageReader source, + BufferedImage theThumbnail, + int minX, int minY, + int width, int height, + int periodX, int periodY, + int[] bands) {} + }); + + reader.addIIOReadWarningListener(new IIOReadWarningListener() { + public void warningOccurred(ImageReader source, String warning) + { + processWarningOccurred(warning); + } + }); + + ImageReadParam param = reader.getDefaultReadParam(); + param.setDestination(bi); + param.setDestinationBands(bmpParam.getDestinationBands()); + param.setDestinationOffset(bmpParam.getDestinationOffset()); + param.setSourceBands(bmpParam.getSourceBands()); + param.setSourceRegion(bmpParam.getSourceRegion()); + param.setSourceSubsampling(bmpParam.getSourceXSubsampling(), + bmpParam.getSourceYSubsampling(), + bmpParam.getSubsamplingXOffset(), + bmpParam.getSubsamplingYOffset()); + reader.read(0, param); + return bi; + } + + private class EmbeddedProgressAdapter implements IIOReadProgressListener { + public void imageComplete(ImageReader src) {} + public void imageProgress(ImageReader src, float percentageDone) {} + public void imageStarted(ImageReader src, int imageIndex) {} + public void thumbnailComplete(ImageReader src) {} + public void thumbnailProgress(ImageReader src, float percentageDone) {} + public void thumbnailStarted(ImageReader src, int iIdx, int tIdx) {} + public void sequenceComplete(ImageReader src) {} + public void sequenceStarted(ImageReader src, int minIndex) {} + public void readAborted(ImageReader src) {} + } + + private static Boolean isLinkedProfileDisabled = null; + + private static boolean isLinkedProfileAllowed() { + if (isLinkedProfileDisabled == null) { + PrivilegedAction a = new PrivilegedAction() { + public Boolean run() { + return Boolean.getBoolean("sun.imageio.plugins.bmp.disableLinkedProfiles"); + } + }; + isLinkedProfileDisabled = AccessController.doPrivileged(a); + } + return !isLinkedProfileDisabled; + } + + private static Boolean isWindowsPlatform = null; + + /** + * Verifies whether the byte array contans a unc path. + * Non-UNC path examples: + * c:\path\to\file - simple notation + * \\?\c:\path\to\file - long notation + * + * UNC path examples: + * \\server\share - a UNC path in simple notation + * \\?\UNC\server\share - a UNC path in long notation + * \\.\some\device - a path to device. + */ + private static boolean isUncOrDevicePath(byte[] p) { + if (isWindowsPlatform == null) { + PrivilegedAction a = new PrivilegedAction() { + public Boolean run() { + String osname = System.getProperty("os.name"); + return (osname != null && + osname.toLowerCase().startsWith("win")); + } + }; + isWindowsPlatform = AccessController.doPrivileged(a); + } + + if (!isWindowsPlatform) { + /* no need for the check on platforms except windows */ + return false; + } + + /* normalize prefix of the path */ + if (p[0] == '/') p[0] = '\\'; + if (p[1] == '/') p[1] = '\\'; + if (p[3] == '/') p[3] = '\\'; + + + if ((p[0] == '\\') && (p[1] == '\\')) { + if ((p[2] == '?') && (p[3] == '\\')) { + // long path: whether unc or local + return ((p[4] == 'U' || p[4] == 'u') && + (p[5] == 'N' || p[5] == 'n') && + (p[6] == 'C' || p[6] == 'c')); + } else { + // device path or short unc notation + return true; + } + } else { + return false; + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageReaderSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageReaderSpi.java new file mode 100644 index 000000000..7e406a043 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageReaderSpi.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +import java.util.Locale; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ServiceRegistry; +import java.io.IOException; +import javax.imageio.ImageReader; +import javax.imageio.IIOException; + +public class BMPImageReaderSpi extends ImageReaderSpi { + + private static String [] writerSpiNames = + {"com.sun.imageio.plugins.bmp.BMPImageWriterSpi"}; + private static String[] formatNames = {"bmp", "BMP"}; + private static String[] entensions = {"bmp"}; + private static String[] mimeType = {"image/bmp"}; + + private boolean registered = false; + + public BMPImageReaderSpi() { + super("Oracle Corporation", + "1.0", + formatNames, + entensions, + mimeType, + "com.sun.imageio.plugins.bmp.BMPImageReader", + new Class[] { ImageInputStream.class }, + writerSpiNames, + false, + null, null, null, null, + true, + BMPMetadata.nativeMetadataFormatName, + "com.sun.imageio.plugins.bmp.BMPMetadataFormat", + null, null); + } + + public void onRegistration(ServiceRegistry registry, + Class category) { + if (registered) { + return; + } + registered = true; + } + + public String getDescription(Locale locale) { + return "Standard BMP Image Reader"; + } + + public boolean canDecodeInput(Object source) throws IOException { + if (!(source instanceof ImageInputStream)) { + return false; + } + + ImageInputStream stream = (ImageInputStream)source; + byte[] b = new byte[2]; + stream.mark(); + stream.readFully(b); + stream.reset(); + + return (b[0] == 0x42) && (b[1] == 0x4d); + } + + public ImageReader createReaderInstance(Object extension) + throws IIOException { + return new BMPImageReader(this); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageWriter.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageWriter.java new file mode 100644 index 000000000..c04e853cb --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageWriter.java @@ -0,0 +1,1517 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +import java.awt.Rectangle; +import java.awt.image.ColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferShort; +import java.awt.image.DataBufferUShort; +import java.awt.image.DirectColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.BandedSampleModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.BufferedImage; + +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.nio.ByteOrder; +import java.util.Iterator; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.event.IIOWriteProgressListener; +import javax.imageio.event.IIOWriteWarningListener; + + +import javax.imageio.plugins.bmp.BMPImageWriteParam; +import com.sun.imageio.plugins.common.ImageUtil; +import com.sun.imageio.plugins.common.I18N; + +/** + * The Java Image IO plugin writer for encoding a binary RenderedImage into + * a BMP format. + * + * The encoding process may clip, subsample using the parameters + * specified in the ImageWriteParam. + * + * @see javax.imageio.plugins.bmp.BMPImageWriteParam + */ +public class BMPImageWriter extends ImageWriter implements BMPConstants { + /** The output stream to write into */ + private ImageOutputStream stream = null; + private ByteArrayOutputStream embedded_stream = null; + private int version; + private int compressionType; + private boolean isTopDown; + private int w, h; + private int compImageSize = 0; + private int[] bitMasks; + private int[] bitPos; + private byte[] bpixels; + private short[] spixels; + private int[] ipixels; + + /** Constructs BMPImageWriter based on the provided + * ImageWriterSpi. + */ + public BMPImageWriter(ImageWriterSpi originator) { + super(originator); + } + + public void setOutput(Object output) { + super.setOutput(output); // validates output + if (output != null) { + if (!(output instanceof ImageOutputStream)) + throw new IllegalArgumentException(I18N.getString("BMPImageWriter0")); + this.stream = (ImageOutputStream)output; + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + } else + this.stream = null; + } + + public ImageWriteParam getDefaultWriteParam() { + return new BMPImageWriteParam(); + } + + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { + return null; + } + + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param) { + BMPMetadata meta = new BMPMetadata(); + meta.bmpVersion = VERSION_3; + meta.compression = getPreferredCompressionType(imageType); + if (param != null + && param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) { + meta.compression = BMPCompressionTypes.getType(param.getCompressionType()); + } + meta.bitsPerPixel = (short)imageType.getColorModel().getPixelSize(); + return meta; + } + + public IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param) { + return null; + } + + public IIOMetadata convertImageMetadata(IIOMetadata metadata, + ImageTypeSpecifier type, + ImageWriteParam param) { + return null; + } + + public boolean canWriteRasters() { + return true; + } + + public void write(IIOMetadata streamMetadata, + IIOImage image, + ImageWriteParam param) throws IOException { + + if (stream == null) { + throw new IllegalStateException(I18N.getString("BMPImageWriter7")); + } + + if (image == null) { + throw new IllegalArgumentException(I18N.getString("BMPImageWriter8")); + } + + clearAbortRequest(); + processImageStarted(0); + if (param == null) + param = getDefaultWriteParam(); + + BMPImageWriteParam bmpParam = (BMPImageWriteParam)param; + + // Default is using 24 bits per pixel. + int bitsPerPixel = 24; + boolean isPalette = false; + int paletteEntries = 0; + IndexColorModel icm = null; + + RenderedImage input = null; + Raster inputRaster = null; + boolean writeRaster = image.hasRaster(); + Rectangle sourceRegion = param.getSourceRegion(); + SampleModel sampleModel = null; + ColorModel colorModel = null; + + compImageSize = 0; + + if (writeRaster) { + inputRaster = image.getRaster(); + sampleModel = inputRaster.getSampleModel(); + colorModel = ImageUtil.createColorModel(null, sampleModel); + if (sourceRegion == null) + sourceRegion = inputRaster.getBounds(); + else + sourceRegion = sourceRegion.intersection(inputRaster.getBounds()); + } else { + input = image.getRenderedImage(); + sampleModel = input.getSampleModel(); + colorModel = input.getColorModel(); + Rectangle rect = new Rectangle(input.getMinX(), input.getMinY(), + input.getWidth(), input.getHeight()); + if (sourceRegion == null) + sourceRegion = rect; + else + sourceRegion = sourceRegion.intersection(rect); + } + + IIOMetadata imageMetadata = image.getMetadata(); + BMPMetadata bmpImageMetadata = null; + if (imageMetadata != null + && imageMetadata instanceof BMPMetadata) + { + bmpImageMetadata = (BMPMetadata)imageMetadata; + } else { + ImageTypeSpecifier imageType = + new ImageTypeSpecifier(colorModel, sampleModel); + + bmpImageMetadata = (BMPMetadata)getDefaultImageMetadata(imageType, + param); + } + + if (sourceRegion.isEmpty()) + throw new RuntimeException(I18N.getString("BMPImageWrite0")); + + int scaleX = param.getSourceXSubsampling(); + int scaleY = param.getSourceYSubsampling(); + int xOffset = param.getSubsamplingXOffset(); + int yOffset = param.getSubsamplingYOffset(); + + // cache the data type; + int dataType = sampleModel.getDataType(); + + sourceRegion.translate(xOffset, yOffset); + sourceRegion.width -= xOffset; + sourceRegion.height -= yOffset; + + int minX = sourceRegion.x / scaleX; + int minY = sourceRegion.y / scaleY; + w = (sourceRegion.width + scaleX - 1) / scaleX; + h = (sourceRegion.height + scaleY - 1) / scaleY; + xOffset = sourceRegion.x % scaleX; + yOffset = sourceRegion.y % scaleY; + + Rectangle destinationRegion = new Rectangle(minX, minY, w, h); + boolean noTransform = destinationRegion.equals(sourceRegion); + + // Raw data can only handle bytes, everything greater must be ASCII. + int[] sourceBands = param.getSourceBands(); + boolean noSubband = true; + int numBands = sampleModel.getNumBands(); + + if (sourceBands != null) { + sampleModel = sampleModel.createSubsetSampleModel(sourceBands); + colorModel = null; + noSubband = false; + numBands = sampleModel.getNumBands(); + } else { + sourceBands = new int[numBands]; + for (int i = 0; i < numBands; i++) + sourceBands[i] = i; + } + + int[] bandOffsets = null; + boolean bgrOrder = true; + + if (sampleModel instanceof ComponentSampleModel) { + bandOffsets = ((ComponentSampleModel)sampleModel).getBandOffsets(); + if (sampleModel instanceof BandedSampleModel) { + // for images with BandedSampleModel we can not work + // with raster directly and must use writePixels() + bgrOrder = false; + } else { + // we can work with raster directly only in case of + // BGR component order. + // In any other case we must use writePixels() + for (int i = 0; i < bandOffsets.length; i++) { + bgrOrder &= (bandOffsets[i] == (bandOffsets.length - i - 1)); + } + } + } else { + if (sampleModel instanceof SinglePixelPackedSampleModel) { + + // BugId 4892214: we can not work with raster directly + // if image have different color order than RGB. + // We should use writePixels() for such images. + int[] bitOffsets = ((SinglePixelPackedSampleModel)sampleModel).getBitOffsets(); + for (int i=0; i bitOffsets[i+1]; + } + } + } + + if (bandOffsets == null) { + // we will use getPixels() to extract pixel data for writePixels() + // Please note that getPixels() provides rgb bands order. + bandOffsets = new int[numBands]; + for (int i = 0; i < numBands; i++) + bandOffsets[i] = i; + } + + noTransform &= bgrOrder; + + int sampleSize[] = sampleModel.getSampleSize(); + + //XXX: check more + + // Number of bytes that a scanline for the image written out will have. + int destScanlineBytes = w * numBands; + + switch(bmpParam.getCompressionMode()) { + case ImageWriteParam.MODE_EXPLICIT: + compressionType = BMPCompressionTypes.getType(bmpParam.getCompressionType()); + break; + case ImageWriteParam.MODE_COPY_FROM_METADATA: + compressionType = bmpImageMetadata.compression; + break; + case ImageWriteParam.MODE_DEFAULT: + compressionType = getPreferredCompressionType(colorModel, sampleModel); + break; + default: + // ImageWriteParam.MODE_DISABLED: + compressionType = BI_RGB; + } + + if (!canEncodeImage(compressionType, colorModel, sampleModel)) { + throw new IOException("Image can not be encoded with compression type " + + BMPCompressionTypes.getName(compressionType)); + } + + byte r[] = null, g[] = null, b[] = null, a[] = null; + + if (compressionType == BI_BITFIELDS) { + bitsPerPixel = + DataBuffer.getDataTypeSize(sampleModel.getDataType()); + + if (bitsPerPixel != 16 && bitsPerPixel != 32) { + // we should use 32bpp images in case of BI_BITFIELD + // compression to avoid color conversion artefacts + bitsPerPixel = 32; + + // Setting this flag to false ensures that generic + // writePixels() will be used to store image data + noTransform = false; + } + + destScanlineBytes = w * bitsPerPixel + 7 >> 3; + + isPalette = true; + paletteEntries = 3; + r = new byte[paletteEntries]; + g = new byte[paletteEntries]; + b = new byte[paletteEntries]; + a = new byte[paletteEntries]; + + int rmask = 0x00ff0000; + int gmask = 0x0000ff00; + int bmask = 0x000000ff; + + if (bitsPerPixel == 16) { + /* NB: canEncodeImage() ensures we have image of + * either USHORT_565_RGB or USHORT_555_RGB type here. + * Technically, it should work for other direct color + * model types but it might be non compatible with win98 + * and friends. + */ + if (colorModel instanceof DirectColorModel) { + DirectColorModel dcm = (DirectColorModel)colorModel; + rmask = dcm.getRedMask(); + gmask = dcm.getGreenMask(); + bmask = dcm.getBlueMask(); + } else { + // it is unlikely, but if it happens, we should throw + // an exception related to unsupported image format + throw new IOException("Image can not be encoded with " + + "compression type " + + BMPCompressionTypes.getName(compressionType)); + } + } + writeMaskToPalette(rmask, 0, r, g, b, a); + writeMaskToPalette(gmask, 1, r, g, b, a); + writeMaskToPalette(bmask, 2, r, g, b, a); + + if (!noTransform) { + // prepare info for writePixels procedure + bitMasks = new int[3]; + bitMasks[0] = rmask; + bitMasks[1] = gmask; + bitMasks[2] = bmask; + + bitPos = new int[3]; + bitPos[0] = firstLowBit(rmask); + bitPos[1] = firstLowBit(gmask); + bitPos[2] = firstLowBit(bmask); + } + + if (colorModel instanceof IndexColorModel) { + icm = (IndexColorModel)colorModel; + } + } else { // handle BI_RGB compression + if (colorModel instanceof IndexColorModel) { + isPalette = true; + icm = (IndexColorModel)colorModel; + paletteEntries = icm.getMapSize(); + + if (paletteEntries <= 2) { + bitsPerPixel = 1; + destScanlineBytes = w + 7 >> 3; + } else if (paletteEntries <= 16) { + bitsPerPixel = 4; + destScanlineBytes = w + 1 >> 1; + } else if (paletteEntries <= 256) { + bitsPerPixel = 8; + } else { + // Cannot be written as a Palette image. So write out as + // 24 bit image. + bitsPerPixel = 24; + isPalette = false; + paletteEntries = 0; + destScanlineBytes = w * 3; + } + + if (isPalette == true) { + r = new byte[paletteEntries]; + g = new byte[paletteEntries]; + b = new byte[paletteEntries]; + a = new byte[paletteEntries]; + + icm.getAlphas(a); + icm.getReds(r); + icm.getGreens(g); + icm.getBlues(b); + } + + } else { + // Grey scale images + if (numBands == 1) { + + isPalette = true; + paletteEntries = 256; + bitsPerPixel = sampleSize[0]; + + destScanlineBytes = (w * bitsPerPixel + 7 >> 3); + + r = new byte[256]; + g = new byte[256]; + b = new byte[256]; + a = new byte[256]; + + for (int i = 0; i < 256; i++) { + r[i] = (byte)i; + g[i] = (byte)i; + b[i] = (byte)i; + a[i] = (byte)255; + } + + } else { + if (sampleModel instanceof SinglePixelPackedSampleModel && + noSubband) + { + /* NB: the actual pixel size can be smaller than + * size of used DataBuffer element. + * For example: in case of TYPE_INT_RGB actual pixel + * size is 24 bits, but size of DataBuffere element + * is 32 bits + */ + int[] sample_sizes = sampleModel.getSampleSize(); + bitsPerPixel = 0; + for (int size : sample_sizes) { + bitsPerPixel += size; + } + bitsPerPixel = roundBpp(bitsPerPixel); + if (bitsPerPixel != DataBuffer.getDataTypeSize(sampleModel.getDataType())) { + noTransform = false; + } + destScanlineBytes = w * bitsPerPixel + 7 >> 3; + } + } + } + } + + // actual writing of image data + int fileSize = 0; + int offset = 0; + int headerSize = 0; + int imageSize = 0; + int xPelsPerMeter = 0; + int yPelsPerMeter = 0; + int colorsUsed = 0; + int colorsImportant = paletteEntries; + + // Calculate padding for each scanline + int padding = destScanlineBytes % 4; + if (padding != 0) { + padding = 4 - padding; + } + + + // FileHeader is 14 bytes, BitmapHeader is 40 bytes, + // add palette size and that is where the data will begin + offset = 54 + paletteEntries * 4; + + imageSize = (destScanlineBytes + padding) * h; + fileSize = imageSize + offset; + headerSize = 40; + + long headPos = stream.getStreamPosition(); + + writeFileHeader(fileSize, offset); + + /* According to MSDN description, the top-down image layout + * is allowed only if compression type is BI_RGB or BI_BITFIELDS. + * Images with any other compression type must be wrote in the + * bottom-up layout. + */ + if (compressionType == BI_RGB || + compressionType == BI_BITFIELDS) + { + isTopDown = bmpParam.isTopDown(); + } else { + isTopDown = false; + } + + writeInfoHeader(headerSize, bitsPerPixel); + + // compression + stream.writeInt(compressionType); + + // imageSize + stream.writeInt(imageSize); + + // xPelsPerMeter + stream.writeInt(xPelsPerMeter); + + // yPelsPerMeter + stream.writeInt(yPelsPerMeter); + + // Colors Used + stream.writeInt(colorsUsed); + + // Colors Important + stream.writeInt(colorsImportant); + + // palette + if (isPalette == true) { + + // write palette + if (compressionType == BI_BITFIELDS) { + // write masks for red, green and blue components. + for (int i=0; i<3; i++) { + int mask = (a[i]&0xFF) + ((r[i]&0xFF)*0x100) + ((g[i]&0xFF)*0x10000) + ((b[i]&0xFF)*0x1000000); + stream.writeInt(mask); + } + } else { + for (int i=0; i maxBandOffset) + maxBandOffset = bandOffsets[i]; + + int[] pixel = new int[maxBandOffset + 1]; + + int destScanlineLength = destScanlineBytes; + + if (noTransform && noSubband) { + destScanlineLength = destScanlineBytes / (DataBuffer.getDataTypeSize(dataType)>>3); + } + for (int i = 0; i < h; i++) { + if (abortRequested()) { + break; + } + + int row = minY + i; + + if (!isTopDown) + row = minY + h - i -1; + + // Get the pixels + Raster src = inputRaster; + + Rectangle srcRect = + new Rectangle(minX * scaleX + xOffset, + row * scaleY + yOffset, + (w - 1)* scaleX + 1, + 1); + if (!writeRaster) + src = input.getData(srcRect); + + if (noTransform && noSubband) { + SampleModel sm = src.getSampleModel(); + int pos = 0; + int startX = srcRect.x - src.getSampleModelTranslateX(); + int startY = srcRect.y - src.getSampleModelTranslateY(); + if (sm instanceof ComponentSampleModel) { + ComponentSampleModel csm = (ComponentSampleModel)sm; + pos = csm.getOffset(startX, startY, 0); + for(int nb=1; nb < csm.getNumBands(); nb++) { + if (pos > csm.getOffset(startX, startY, nb)) { + pos = csm.getOffset(startX, startY, nb); + } + } + } else if (sm instanceof MultiPixelPackedSampleModel) { + MultiPixelPackedSampleModel mppsm = + (MultiPixelPackedSampleModel)sm; + pos = mppsm.getOffset(startX, startY); + } else if (sm instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = + (SinglePixelPackedSampleModel)sm; + pos = sppsm.getOffset(startX, startY); + } + + if (compressionType == BI_RGB || compressionType == BI_BITFIELDS){ + switch(dataType) { + case DataBuffer.TYPE_BYTE: + byte[] bdata = + ((DataBufferByte)src.getDataBuffer()).getData(); + stream.write(bdata, pos, destScanlineLength); + break; + + case DataBuffer.TYPE_SHORT: + short[] sdata = + ((DataBufferShort)src.getDataBuffer()).getData(); + stream.writeShorts(sdata, pos, destScanlineLength); + break; + + case DataBuffer.TYPE_USHORT: + short[] usdata = + ((DataBufferUShort)src.getDataBuffer()).getData(); + stream.writeShorts(usdata, pos, destScanlineLength); + break; + + case DataBuffer.TYPE_INT: + int[] idata = + ((DataBufferInt)src.getDataBuffer()).getData(); + stream.writeInts(idata, pos, destScanlineLength); + break; + } + + for(int k=0; k>>= 1; + } + return count; + } + + private class IIOWriteProgressAdapter implements IIOWriteProgressListener { + + public void imageComplete(ImageWriter source) { + } + + public void imageProgress(ImageWriter source, float percentageDone) { + } + + public void imageStarted(ImageWriter source, int imageIndex) { + } + + public void thumbnailComplete(ImageWriter source) { + } + + public void thumbnailProgress(ImageWriter source, float percentageDone) { + } + + public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) { + } + + public void writeAborted(ImageWriter source) { + } + } + + /* + * Returns preferred compression type for given image. + * The default compression type is BI_RGB, but some image types can't be + * encodeed with using default compression without cahnge color resolution. + * For example, TYPE_USHORT_565_RGB may be encodeed only by using BI_BITFIELDS + * compression type. + * + * NB: we probably need to extend this method if we encounter other image + * types which can not be encoded with BI_RGB compression type. + */ + protected int getPreferredCompressionType(ColorModel cm, SampleModel sm) { + ImageTypeSpecifier imageType = new ImageTypeSpecifier(cm, sm); + return getPreferredCompressionType(imageType); + } + + protected int getPreferredCompressionType(ImageTypeSpecifier imageType) { + if (imageType.getBufferedImageType() == BufferedImage.TYPE_USHORT_565_RGB) { + return BI_BITFIELDS; + } + return BI_RGB; + } + + /* + * Check whether we can encode image of given type using compression method in question. + * + * For example, TYPE_USHORT_565_RGB can be encodeed with BI_BITFIELDS compression only. + * + * NB: method should be extended if other cases when we can not encode + * with given compression will be discovered. + */ + protected boolean canEncodeImage(int compression, ColorModel cm, SampleModel sm) { + ImageTypeSpecifier imgType = new ImageTypeSpecifier(cm, sm); + return canEncodeImage(compression, imgType); + } + + protected boolean canEncodeImage(int compression, ImageTypeSpecifier imgType) { + ImageWriterSpi spi = this.getOriginatingProvider(); + if (!spi.canEncodeImage(imgType)) { + return false; + } + int biType = imgType.getBufferedImageType(); + int bpp = imgType.getColorModel().getPixelSize(); + if (compressionType == BI_RLE4 && bpp != 4) { + // only 4bpp images can be encoded as BI_RLE4 + return false; + } + if (compressionType == BI_RLE8 && bpp != 8) { + // only 8bpp images can be encoded as BI_RLE8 + return false; + } + if (bpp == 16) { + /* + * Technically we expect that we may be able to + * encode only some of SinglePixelPackedSampleModel + * images here. + * + * In addition we should take into account following: + * + * 1. BI_RGB case, according to the MSDN description: + * + * The bitmap has a maximum of 2^16 colors. If the + * biCompression member of the BITMAPINFOHEADER is BI_RGB, + * the bmiColors member of BITMAPINFO is NULL. Each WORD + * in the bitmap array represents a single pixel. The + * relative intensities of red, green, and blue are + * represented with five bits for each color component. + * + * 2. BI_BITFIELDS case, according ot the MSDN description: + * + * Windows 95/98/Me: When the biCompression member is + * BI_BITFIELDS, the system supports only the following + * 16bpp color masks: A 5-5-5 16-bit image, where the blue + * mask is 0x001F, the green mask is 0x03E0, and the red mask + * is 0x7C00; and a 5-6-5 16-bit image, where the blue mask + * is 0x001F, the green mask is 0x07E0, and the red mask is + * 0xF800. + */ + boolean canUseRGB = false; + boolean canUseBITFIELDS = false; + + SampleModel sm = imgType.getSampleModel(); + if (sm instanceof SinglePixelPackedSampleModel) { + int[] sizes = + ((SinglePixelPackedSampleModel)sm).getSampleSize(); + + canUseRGB = true; + canUseBITFIELDS = true; + for (int i = 0; i < sizes.length; i++) { + canUseRGB &= (sizes[i] == 5); + canUseBITFIELDS &= ((sizes[i] == 5) || + (i == 1 && sizes[i] == 6)); + } + } + + return (((compressionType == BI_RGB) && canUseRGB) || + ((compressionType == BI_BITFIELDS) && canUseBITFIELDS)); + } + return true; + } + + protected void writeMaskToPalette(int mask, int i, + byte[] r, byte[]g, byte[] b, byte[]a) { + b[i] = (byte)(0xff & (mask >> 24)); + g[i] = (byte)(0xff & (mask >> 16)); + r[i] = (byte)(0xff & (mask >> 8)); + a[i] = (byte)(0xff & mask); + } + + private int roundBpp(int x) { + if (x <= 8) { + return 8; + } else if (x <= 16) { + return 16; + } if (x <= 24) { + return 24; + } else { + return 32; + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageWriterSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageWriterSpi.java new file mode 100644 index 000000000..983183df9 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPImageWriterSpi.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +import java.awt.image.DataBuffer; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; + +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.spi.IIORegistry; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.ImageWriter; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.IIOException; +import java.util.Locale; + +import javax.imageio.plugins.bmp.BMPImageWriteParam; + +public class BMPImageWriterSpi extends ImageWriterSpi { + private static String [] readerSpiNames = + {"com.sun.imageio.plugins.bmp.BMPImageReaderSpi"}; + private static String[] formatNames = {"bmp", "BMP"}; + private static String[] entensions = {"bmp"}; + private static String[] mimeType = {"image/bmp"}; + + private boolean registered = false; + + public BMPImageWriterSpi() { + super("Oracle Corporation", + "1.0", + formatNames, + entensions, + mimeType, + "com.sun.imageio.plugins.bmp.BMPImageWriter", + new Class[] { ImageOutputStream.class }, + readerSpiNames, + false, + null, null, null, null, + true, + BMPMetadata.nativeMetadataFormatName, + "com.sun.imageio.plugins.bmp.BMPMetadataFormat", + null, null); + } + + public String getDescription(Locale locale) { + return "Standard BMP Image Writer"; + } + + public void onRegistration(ServiceRegistry registry, + Class category) { + if (registered) { + return; + } + + registered = true; + } + + public boolean canEncodeImage(ImageTypeSpecifier type) { + int dataType= type.getSampleModel().getDataType(); + if (dataType < DataBuffer.TYPE_BYTE || dataType > DataBuffer.TYPE_INT) + return false; + + SampleModel sm = type.getSampleModel(); + int numBands = sm.getNumBands(); + if (!(numBands == 1 || numBands == 3)) + return false; + + if (numBands == 1 && dataType != DataBuffer.TYPE_BYTE) + return false; + + if (dataType > DataBuffer.TYPE_BYTE && + !(sm instanceof SinglePixelPackedSampleModel)) + return false; + + return true; + } + + public ImageWriter createWriterInstance(Object extension) + throws IIOException { + return new BMPImageWriter(this); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadata.java new file mode 100644 index 000000000..6a937b316 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadata.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import org.w3c.dom.Node; +import com.sun.imageio.plugins.common.I18N; + +import com.sun.imageio.plugins.common.ImageUtil; + +public class BMPMetadata extends IIOMetadata implements BMPConstants { + public static final String nativeMetadataFormatName = + "javax_imageio_bmp_1.0"; + + // Fields for Image Descriptor + public String bmpVersion; + public int width ; + public int height; + public short bitsPerPixel; + public int compression; + public int imageSize; + + // Fields for PixelsPerMeter + public int xPixelsPerMeter; + public int yPixelsPerMeter; + + public int colorsUsed; + public int colorsImportant; + + // Fields for BI_BITFIELDS compression(Mask) + public int redMask; + public int greenMask; + public int blueMask; + public int alphaMask; + + public int colorSpace; + + // Fields for CIE XYZ for the LCS_CALIBRATED_RGB color space + public double redX; + public double redY; + public double redZ; + public double greenX; + public double greenY; + public double greenZ; + public double blueX; + public double blueY; + public double blueZ; + + // Fields for Gamma values for the LCS_CALIBRATED_RGB color space + public int gammaRed; + public int gammaGreen; + public int gammaBlue; + + public int intent; + + // Fields for the Palette and Entries + public byte[] palette = null; + public int paletteSize; + public int red; + public int green; + public int blue; + + // Fields from CommentExtension + // List of byte[] + public List comments = null; // new ArrayList(); + + public BMPMetadata() { + super(true, + nativeMetadataFormatName, + "com.sun.imageio.plugins.bmp.BMPMetadataFormat", + null, null); + } + + public boolean isReadOnly() { + return true; + } + + public Node getAsTree(String formatName) { + if (formatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } else { + throw new IllegalArgumentException(I18N.getString("BMPMetadata0")); + } + } + + private String toISO8859(byte[] data) { + try { + return new String(data, "ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + private Node getNativeTree() { + IIOMetadataNode root = + new IIOMetadataNode(nativeMetadataFormatName); + + addChildNode(root, "BMPVersion", bmpVersion); + addChildNode(root, "Width", new Integer(width)); + addChildNode(root, "Height", new Integer(height)); + addChildNode(root, "BitsPerPixel", new Short(bitsPerPixel)); + addChildNode(root, "Compression", new Integer(compression)); + addChildNode(root, "ImageSize", new Integer(imageSize)); + + IIOMetadataNode node = addChildNode(root, "PixelsPerMeter", null); + addChildNode(node, "X", new Integer(xPixelsPerMeter)); + addChildNode(node, "Y", new Integer(yPixelsPerMeter)); + + addChildNode(root, "ColorsUsed", new Integer(colorsUsed)); + addChildNode(root, "ColorsImportant", new Integer(colorsImportant)); + + int version = 0; + for (int i = 0; i < bmpVersion.length(); i++) + if (Character.isDigit(bmpVersion.charAt(i))) + version = bmpVersion.charAt(i) -'0'; + + if (version >= 4) { + node = addChildNode(root, "Mask", null); + addChildNode(node, "Red", new Integer(redMask)); + addChildNode(node, "Green", new Integer(greenMask)); + addChildNode(node, "Blue", new Integer(blueMask)); + addChildNode(node, "Alpha", new Integer(alphaMask)); + + addChildNode(root, "ColorSpaceType", new Integer(colorSpace)); + + node = addChildNode(root, "CIEXYZEndPoints", null); + addXYZPoints(node, "Red", redX, redY, redZ); + addXYZPoints(node, "Green", greenX, greenY, greenZ); + addXYZPoints(node, "Blue", blueX, blueY, blueZ); + + node = addChildNode(root, "Intent", new Integer(intent)); + } + + // Palette + if ((palette != null) && (paletteSize > 0)) { + node = addChildNode(root, "Palette", null); + int numComps = palette.length / paletteSize; + + for (int i = 0, j = 0; i < paletteSize; i++) { + IIOMetadataNode entry = + addChildNode(node, "PaletteEntry", null); + red = palette[j++] & 0xff; + green = palette[j++] & 0xff; + blue = palette[j++] & 0xff; + addChildNode(entry, "Red", new Byte((byte)red)); + addChildNode(entry, "Green", new Byte((byte)green)); + addChildNode(entry, "Blue", new Byte((byte)blue)); + if (numComps == 4) + addChildNode(entry, "Alpha", + new Byte((byte)(palette[j++] & 0xff))); + } + } + + return root; + } + + // Standard tree node methods + protected IIOMetadataNode getStandardChromaNode() { + + if ((palette != null) && (paletteSize > 0)) { + IIOMetadataNode node = new IIOMetadataNode("Chroma"); + IIOMetadataNode subNode = new IIOMetadataNode("Palette"); + int numComps = palette.length / paletteSize; + subNode.setAttribute("value", "" + numComps); + + for (int i = 0, j = 0; i < paletteSize; i++) { + IIOMetadataNode subNode1 = new IIOMetadataNode("PaletteEntry"); + subNode1.setAttribute("index", ""+i); + subNode1.setAttribute("red", "" + palette[j++]); + subNode1.setAttribute("green", "" + palette[j++]); + subNode1.setAttribute("blue", "" + palette[j++]); + if (numComps == 4 && palette[j] != 0) + subNode1.setAttribute("alpha", "" + palette[j++]); + subNode.appendChild(subNode1); + } + node.appendChild(subNode); + return node; + } + + return null; + } + + protected IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode node = new IIOMetadataNode("Compression"); + + // CompressionTypeName + IIOMetadataNode subNode = new IIOMetadataNode("CompressionTypeName"); + subNode.setAttribute("value", BMPCompressionTypes.getName(compression)); + node.appendChild(subNode); + return node; + } + + protected IIOMetadataNode getStandardDataNode() { + IIOMetadataNode node = new IIOMetadataNode("Data"); + + String bits = ""; + if (bitsPerPixel == 24) + bits = "8 8 8 "; + else if (bitsPerPixel == 16 || bitsPerPixel == 32) { + bits = "" + countBits(redMask) + " " + countBits(greenMask) + + countBits(blueMask) + "" + countBits(alphaMask); + } + + IIOMetadataNode subNode = new IIOMetadataNode("BitsPerSample"); + subNode.setAttribute("value", bits); + node.appendChild(subNode); + + return node; + } + + protected IIOMetadataNode getStandardDimensionNode() { + if (yPixelsPerMeter > 0.0F && xPixelsPerMeter > 0.0F) { + IIOMetadataNode node = new IIOMetadataNode("Dimension"); + float ratio = yPixelsPerMeter / xPixelsPerMeter; + IIOMetadataNode subNode = new IIOMetadataNode("PixelAspectRatio"); + subNode.setAttribute("value", "" + ratio); + node.appendChild(subNode); + + subNode = new IIOMetadataNode("HorizontalPhysicalPixelSpacing"); + subNode.setAttribute("value", "" + (1 / xPixelsPerMeter * 1000)); + node.appendChild(subNode); + + subNode = new IIOMetadataNode("VerticalPhysicalPixelSpacing"); + subNode.setAttribute("value", "" + (1 / yPixelsPerMeter * 1000)); + node.appendChild(subNode); + + return node; + } + return null; + } + + public void setFromTree(String formatName, Node root) { + throw new IllegalStateException(I18N.getString("BMPMetadata1")); + } + + public void mergeTree(String formatName, Node root) { + throw new IllegalStateException(I18N.getString("BMPMetadata1")); + } + + public void reset() { + throw new IllegalStateException(I18N.getString("BMPMetadata1")); + } + + private String countBits(int num) { + int count = 0; + while(num > 0) { + if ((num & 1) == 1) + count++; + num >>>= 1; + } + + return count == 0 ? "" : "" + count; + } + + private void addXYZPoints(IIOMetadataNode root, String name, double x, double y, double z) { + IIOMetadataNode node = addChildNode(root, name, null); + addChildNode(node, "X", new Double(x)); + addChildNode(node, "Y", new Double(y)); + addChildNode(node, "Z", new Double(z)); + } + + private IIOMetadataNode addChildNode(IIOMetadataNode root, + String name, + Object object) { + IIOMetadataNode child = new IIOMetadataNode(name); + if (object != null) { + child.setUserObject(object); + child.setNodeValue(ImageUtil.convertObjectToString(object)); + } + root.appendChild(child); + return child; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadataFormat.java new file mode 100644 index 000000000..b9ea0ee0f --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadataFormat.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2003, 2004, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +import java.util.Arrays; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class BMPMetadataFormat extends IIOMetadataFormatImpl { + + private static IIOMetadataFormat instance = null; + + private BMPMetadataFormat() { + super(BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> ImageDescriptor + addElement("ImageDescriptor", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("ImageDescriptor", "bmpVersion", + DATATYPE_STRING, true, null); + addAttribute("ImageDescriptor", "width", + DATATYPE_INTEGER, true, null, + "0", "65535", true, true); + addAttribute("ImageDescriptor", "height", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("ImageDescriptor", "bitsPerPixel", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("ImageDescriptor", "compression", + DATATYPE_INTEGER, false, null); + addAttribute("ImageDescriptor", "imageSize", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + + addElement("PixelsPerMeter", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("PixelsPerMeter", "X", + DATATYPE_INTEGER, false, null, + "1", "65535", true, true); + addAttribute("PixelsPerMeter", "Y", + DATATYPE_INTEGER, false, null, + "1", "65535", true, true); + + addElement("ColorsUsed", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("ColorsUsed", "value", + DATATYPE_INTEGER, true, null, + "0", "65535", true, true); + + addElement("ColorsImportant", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("ColorsImportant", "value", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + + addElement("BI_BITFIELDS_Mask", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("BI_BITFIELDS_Mask", "red", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + addAttribute("BI_BITFIELDS_Mask", "green", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + addAttribute("BI_BITFIELDS_Mask", "blue", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + + addElement("ColorSpace", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("ColorSpace", "value", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + + addElement("LCS_CALIBRATED_RGB", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + /// Should the max value be 1.7976931348623157e+308 ? + addAttribute("LCS_CALIBRATED_RGB", "redX", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB", "redY", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB", "redZ", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB", "greenX", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB", "greenY", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB", "greenZ", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB", "blueX", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB", "blueY", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB", "blueZ", + DATATYPE_DOUBLE, false, null, + "0", "65535", true, true); + + addElement("LCS_CALIBRATED_RGB_GAMMA", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("LCS_CALIBRATED_RGB_GAMMA","red", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB_GAMMA","green", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + addAttribute("LCS_CALIBRATED_RGB_GAMMA","blue", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + + addElement("Intent", + BMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("Intent", "value", + DATATYPE_INTEGER, false, null, + "0", "65535", true, true); + + // root -> Palette + addElement("Palette", + BMPMetadata.nativeMetadataFormatName, + 2, 256); + addAttribute("Palette", "sizeOfPalette", + DATATYPE_INTEGER, true, null); + addBooleanAttribute("Palette", "sortFlag", + false, false); + + // root -> Palette -> PaletteEntry + addElement("PaletteEntry", "Palette", + CHILD_POLICY_EMPTY); + addAttribute("PaletteEntry", "index", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("PaletteEntry", "red", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("PaletteEntry", "green", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("PaletteEntry", "blue", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + + + // root -> CommentExtensions + addElement("CommentExtensions", + BMPMetadata.nativeMetadataFormatName, + 1, Integer.MAX_VALUE); + + // root -> CommentExtensions -> CommentExtension + addElement("CommentExtension", "CommentExtensions", + CHILD_POLICY_EMPTY); + addAttribute("CommentExtension", "value", + DATATYPE_STRING, true, null); + } + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + return true; + } + + public static synchronized IIOMetadataFormat getInstance() { + if (instance == null) { + instance = new BMPMetadataFormat(); + } + return instance; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadataFormatResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadataFormatResources.java new file mode 100644 index 000000000..1f2f444ac --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/bmp/BMPMetadataFormatResources.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2003, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.bmp; + +import java.util.ListResourceBundle; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class BMPMetadataFormatResources extends ListResourceBundle { + + public BMPMetadataFormatResources() {} + + protected Object[][] getContents() { + return new Object[][] { + + // Node name, followed by description + { "BMPVersion", "BMP version string" }, + { "Width", "The width of the image" }, + { "Height","The height of the image" }, + { "BitsPerPixel", "" }, + { "PixelsPerMeter", "Resolution in pixels per unit distance" }, + { "X", "Pixels Per Meter along X" }, + { "Y", "Pixels Per Meter along Y" }, + { "ColorsUsed", + "Number of color indexes in the color table actually used" }, + { "ColorsImportant", + "Number of color indexes considered important for display" }, + { "Mask", + "Color masks; present for BI_BITFIELDS compression only"}, + + { "Intent", "Rendering intent" }, + { "Palette", "The color palette" }, + + { "Red", "Red Mask/Color Palette" }, + { "Green", "Green Mask/Color Palette/Gamma" }, + { "Blue", "Blue Mask/Color Palette/Gamma" }, + { "Alpha", "Alpha Mask/Color Palette/Gamma" }, + + { "ColorSpaceType", "Color Space Type" }, + + { "X", "The X coordinate of a point in XYZ color space" }, + { "Y", "The Y coordinate of a point in XYZ color space" }, + { "Z", "The Z coordinate of a point in XYZ color space" }, + }; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/BitFile.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/BitFile.java new file mode 100644 index 000000000..fb6c25a1d --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/BitFile.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.io.IOException; +import javax.imageio.stream.ImageOutputStream; + +/* + * Came from GIFEncoder initially. + * Modified - to allow for output compressed data without the block counts + * which breakup the compressed data stream for GIF. + */ +public class BitFile { + ImageOutputStream output; + byte buffer[]; + int index; + int bitsLeft; // bits left at current index that are avail. + + /** note this also indicates gif format BITFile. **/ + boolean blocks = false; + + /* + * @param output destination for output data + * @param blocks GIF LZW requires block counts for output data + */ + public BitFile(ImageOutputStream output, boolean blocks) { + this.output = output; + this.blocks = blocks; + buffer = new byte[256]; + index = 0; + bitsLeft = 8; + } + + public void flush() throws IOException { + int numBytes = index + (bitsLeft == 8 ? 0 : 1); + if (numBytes > 0) { + if (blocks) { + output.write(numBytes); + } + output.write(buffer, 0, numBytes); + buffer[0] = 0; + index = 0; + bitsLeft = 8; + } + } + + public void writeBits(int bits, int numbits) throws IOException { + int bitsWritten = 0; + int numBytes = 255; // gif block count + do { + // This handles the GIF block count stuff + if ((index == 254 && bitsLeft == 0) || index > 254) { + if (blocks) { + output.write(numBytes); + } + + output.write(buffer, 0, numBytes); + + buffer[0] = 0; + index = 0; + bitsLeft = 8; + } + + if (numbits <= bitsLeft) { // bits contents fit in current index byte + if (blocks) { // GIF + buffer[index] |= (bits & ((1 << numbits) - 1)) << (8 - bitsLeft); + bitsWritten += numbits; + bitsLeft -= numbits; + numbits = 0; + } else { + buffer[index] |= (bits & ((1 << numbits) - 1)) << (bitsLeft - numbits); + bitsWritten += numbits; + bitsLeft -= numbits; + numbits = 0; + } + } else { // bits overflow from current byte to next. + if (blocks) { // GIF + // if bits > space left in current byte then the lowest order bits + // of code are taken and put in current byte and rest put in next. + buffer[index] |= (bits & ((1 << bitsLeft) - 1)) << (8 - bitsLeft); + bitsWritten += bitsLeft; + bits >>= bitsLeft; + numbits -= bitsLeft; + buffer[++index] = 0; + bitsLeft = 8; + } else { + // if bits > space left in current byte then the highest order bits + // of code are taken and put in current byte and rest put in next. + // at highest order bit location !! + int topbits = (bits >>> (numbits - bitsLeft)) & ((1 << bitsLeft) - 1); + buffer[index] |= topbits; + numbits -= bitsLeft; // ok this many bits gone off the top + bitsWritten += bitsLeft; + buffer[++index] = 0; // next index + bitsLeft = 8; + } + } + } while (numbits != 0); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/BogusColorSpace.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/BogusColorSpace.java new file mode 100644 index 000000000..ea7fa4f02 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/BogusColorSpace.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2003, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.awt.color.ColorSpace; + +/** + * A dummy ColorSpace to enable ColorModel + * for image data which do not have an innate color representation. + */ +public class BogusColorSpace extends ColorSpace { + /** + * Return the type given the number of components. + * + * @param numComponents The number of components in the + * ColorSpace. + * @exception IllegalArgumentException if numComponents + * is less than 1. + */ + private static int getType(int numComponents) { + if(numComponents < 1) { + throw new IllegalArgumentException("numComponents < 1!"); + } + + int type; + switch(numComponents) { + case 1: + type = ColorSpace.TYPE_GRAY; + break; + default: + // Based on the constant definitions TYPE_2CLR=12 through + // TYPE_FCLR=25. This will return unknown types for + // numComponents > 15. + type = numComponents + 10; + } + + return type; + } + + /** + * Constructs a bogus ColorSpace. + * + * @param numComponents The number of components in the + * ColorSpace. + * @exception IllegalArgumentException if numComponents + * is less than 1. + */ + public BogusColorSpace(int numComponents) { + super(getType(numComponents), numComponents); + } + + // + // The following methods simply copy the input array to the + // output array while otherwise attempting to adhere to the + // specified behavior of the methods vis-a-vis exceptions. + // + + public float[] toRGB(float[] colorvalue) { + if(colorvalue.length < getNumComponents()) { + throw new ArrayIndexOutOfBoundsException + ("colorvalue.length < getNumComponents()"); + } + + float[] rgbvalue = new float[3]; + + System.arraycopy(colorvalue, 0, rgbvalue, 0, + Math.min(3, getNumComponents())); + + return colorvalue; + } + + public float[] fromRGB(float[] rgbvalue) { + if(rgbvalue.length < 3) { + throw new ArrayIndexOutOfBoundsException + ("rgbvalue.length < 3"); + } + + float[] colorvalue = new float[getNumComponents()]; + + System.arraycopy(rgbvalue, 0, colorvalue, 0, + Math.min(3, colorvalue.length)); + + return rgbvalue; + } + + public float[] toCIEXYZ(float[] colorvalue) { + if(colorvalue.length < getNumComponents()) { + throw new ArrayIndexOutOfBoundsException + ("colorvalue.length < getNumComponents()"); + } + + float[] xyzvalue = new float[3]; + + System.arraycopy(colorvalue, 0, xyzvalue, 0, + Math.min(3, getNumComponents())); + + return colorvalue; + } + + public float[] fromCIEXYZ(float[] xyzvalue) { + if(xyzvalue.length < 3) { + throw new ArrayIndexOutOfBoundsException + ("xyzvalue.length < 3"); + } + + float[] colorvalue = new float[getNumComponents()]; + + System.arraycopy(xyzvalue, 0, colorvalue, 0, + Math.min(3, colorvalue.length)); + + return xyzvalue; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/I18N.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/I18N.java new file mode 100644 index 000000000..7d55e35ca --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/I18N.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2003, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +public final class I18N extends I18NImpl { + private final static String resource_name = "iio-plugin.properties"; + public static String getString(String key) { + return getString("com.sun.imageio.plugins.common.I18N", resource_name, key); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/I18NImpl.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/I18NImpl.java new file mode 100644 index 000000000..4c6d36ac6 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/I18NImpl.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2003, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.io.InputStream; +import java.util.PropertyResourceBundle; +import java.net.URL; + +/** + * Class to simplify use of internationalization message strings. + * Property files are constructed in terms of content as for JAI with + * one "key=value" pair per line. All such files however have the same + * name "properties". The resource extractor resolves the extraction of + * the file from the jar as the package name is included automatically. + * + *

Extenders need only provide a static method + * getString(String) which calls the static method in this + * class with the name of the invoking class and returns a + * String. + */ +public class I18NImpl { + /** + * Returns the message string with the specified key from the + * "properties" file in the package containing the class with + * the specified name. + */ + protected static final String getString(String className, String resource_name, String key) { + PropertyResourceBundle bundle = null; + try { + InputStream stream = + Class.forName(className).getResourceAsStream(resource_name); + bundle = new PropertyResourceBundle(stream); + } catch(Throwable e) { + throw new RuntimeException(e); // Chain the exception. + } + + return (String)bundle.handleGetObject(key); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/ImageUtil.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/ImageUtil.java new file mode 100644 index 000000000..2c27e56e6 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/ImageUtil.java @@ -0,0 +1,1167 @@ +/* + * Copyright (c) 2003, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferShort; +import java.awt.image.DataBufferUShort; +import java.awt.image.DirectColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.WritableRaster; +import java.util.Arrays; + +//import javax.imageio.ImageTypeSpecifier; + +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; + +public class ImageUtil { + /* XXX testing only + public static void main(String[] args) { + ImageTypeSpecifier bilevel = + ImageTypeSpecifier.createIndexed(new byte[] {(byte)0, (byte)255}, + new byte[] {(byte)0, (byte)255}, + new byte[] {(byte)0, (byte)255}, + null, 1, + DataBuffer.TYPE_BYTE); + ImageTypeSpecifier gray = + ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false); + ImageTypeSpecifier grayAlpha = + ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false, + false); + ImageTypeSpecifier rgb = + ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), + new int[] {0, 1, 2}, + DataBuffer.TYPE_BYTE, + false, + false); + ImageTypeSpecifier rgba = + ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), + new int[] {0, 1, 2, 3}, + DataBuffer.TYPE_BYTE, + true, + false); + ImageTypeSpecifier packed = + ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), + 0xff000000, + 0x00ff0000, + 0x0000ff00, + 0x000000ff, + DataBuffer.TYPE_BYTE, + false); + + SampleModel bandedSM = + new java.awt.image.BandedSampleModel(DataBuffer.TYPE_BYTE, + 1, 1, 15); + + System.out.println(createColorModel(bilevel.getSampleModel())); + System.out.println(createColorModel(gray.getSampleModel())); + System.out.println(createColorModel(grayAlpha.getSampleModel())); + System.out.println(createColorModel(rgb.getSampleModel())); + System.out.println(createColorModel(rgba.getSampleModel())); + System.out.println(createColorModel(packed.getSampleModel())); + System.out.println(createColorModel(bandedSM)); + } + */ + + /** + * Creates a ColorModel that may be used with the + * specified SampleModel. If a suitable + * ColorModel cannot be found, this method returns + * null. + * + *

Suitable ColorModels are guaranteed to exist + * for all instances of ComponentSampleModel. + * For 1- and 3- banded SampleModels, the returned + * ColorModel will be opaque. For 2- and 4-banded + * SampleModels, the output will use alpha transparency + * which is not premultiplied. 1- and 2-banded data will use a + * grayscale ColorSpace, and 3- and 4-banded data a sRGB + * ColorSpace. Data with 5 or more bands will have a + * BogusColorSpace.

+ * + *

An instance of DirectColorModel will be created for + * instances of SinglePixelPackedSampleModel with no more + * than 4 bands.

+ * + *

An instance of IndexColorModel will be created for + * instances of MultiPixelPackedSampleModel. The colormap + * will be a grayscale ramp with 1 << numberOfBits + * entries ranging from zero to at most 255.

+ * + * @return An instance of ColorModel that is suitable for + * the supplied SampleModel, or null. + * + * @throws IllegalArgumentException If sampleModel is + * null. + */ + public static final ColorModel createColorModel(SampleModel sampleModel) { + // Check the parameter. + if(sampleModel == null) { + throw new IllegalArgumentException("sampleModel == null!"); + } + + // Get the data type. + int dataType = sampleModel.getDataType(); + + // Check the data type + switch(dataType) { + case DataBuffer.TYPE_BYTE: + case DataBuffer.TYPE_USHORT: + case DataBuffer.TYPE_SHORT: + case DataBuffer.TYPE_INT: + case DataBuffer.TYPE_FLOAT: + case DataBuffer.TYPE_DOUBLE: + break; + default: + // Return null for other types. + return null; + } + + // The return variable. + ColorModel colorModel = null; + + // Get the sample size. + int[] sampleSize = sampleModel.getSampleSize(); + + // Create a Component ColorModel. + if(sampleModel instanceof ComponentSampleModel) { + // Get the number of bands. + int numBands = sampleModel.getNumBands(); + + // Determine the color space. + ColorSpace colorSpace = null; + if(numBands <= 2) { + colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY); + } else if(numBands <= 4) { + colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); + } else { + colorSpace = new BogusColorSpace(numBands); + } + + boolean hasAlpha = (numBands == 2) || (numBands == 4); + boolean isAlphaPremultiplied = false; + int transparency = hasAlpha ? + Transparency.TRANSLUCENT : Transparency.OPAQUE; + + colorModel = new ComponentColorModel(colorSpace, + sampleSize, + hasAlpha, + isAlphaPremultiplied, + transparency, + dataType); + } else if (sampleModel.getNumBands() <= 4 && + sampleModel instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = + (SinglePixelPackedSampleModel)sampleModel; + + int[] bitMasks = sppsm.getBitMasks(); + int rmask = 0; + int gmask = 0; + int bmask = 0; + int amask = 0; + + int numBands = bitMasks.length; + if (numBands <= 2) { + rmask = gmask = bmask = bitMasks[0]; + if (numBands == 2) { + amask = bitMasks[1]; + } + } else { + rmask = bitMasks[0]; + gmask = bitMasks[1]; + bmask = bitMasks[2]; + if (numBands == 4) { + amask = bitMasks[3]; + } + } + + int bits = 0; + for (int i = 0; i < sampleSize.length; i++) { + bits += sampleSize[i]; + } + + return new DirectColorModel(bits, rmask, gmask, bmask, amask); + + } else if(sampleModel instanceof MultiPixelPackedSampleModel) { + // Load the colormap with a ramp. + int bitsPerSample = sampleSize[0]; + int numEntries = 1 << bitsPerSample; + byte[] map = new byte[numEntries]; + for (int i = 0; i < numEntries; i++) { + map[i] = (byte)(i*255/(numEntries - 1)); + } + + colorModel = new IndexColorModel(bitsPerSample, numEntries, + map, map, map); + + } + + return colorModel; + } + + /** + * For the case of binary data (isBinary() returns + * true), return the binary data as a packed byte array. + * The data will be packed as eight bits per byte with no bit offset, + * i.e., the first bit in each image line will be the left-most of the + * first byte of the line. The line stride in bytes will be + * (int)((getWidth()+7)/8). The length of the returned + * array will be the line stride multiplied by getHeight() + * + * @return the binary data as a packed array of bytes with zero offset + * of null if the data are not binary. + * @throws IllegalArgumentException if isBinary() returns + * false with the SampleModel of the + * supplied Raster as argument. + */ + public static byte[] getPackedBinaryData(Raster raster, + Rectangle rect) { + SampleModel sm = raster.getSampleModel(); + if(!isBinary(sm)) { + throw new IllegalArgumentException(I18N.getString("ImageUtil0")); + } + + int rectX = rect.x; + int rectY = rect.y; + int rectWidth = rect.width; + int rectHeight = rect.height; + + DataBuffer dataBuffer = raster.getDataBuffer(); + + int dx = rectX - raster.getSampleModelTranslateX(); + int dy = rectY - raster.getSampleModelTranslateY(); + + MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm; + int lineStride = mpp.getScanlineStride(); + int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); + int bitOffset = mpp.getBitOffset(dx); + + int numBytesPerRow = (rectWidth + 7)/8; + if(dataBuffer instanceof DataBufferByte && + eltOffset == 0 && bitOffset == 0 && + numBytesPerRow == lineStride && + ((DataBufferByte)dataBuffer).getData().length == + numBytesPerRow*rectHeight) { + return ((DataBufferByte)dataBuffer).getData(); + } + + byte[] binaryDataArray = new byte[numBytesPerRow*rectHeight]; + + int b = 0; + + if(bitOffset == 0) { + if(dataBuffer instanceof DataBufferByte) { + byte[] data = ((DataBufferByte)dataBuffer).getData(); + int stride = numBytesPerRow; + int offset = 0; + for(int y = 0; y < rectHeight; y++) { + System.arraycopy(data, eltOffset, + binaryDataArray, offset, + stride); + offset += stride; + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferShort || + dataBuffer instanceof DataBufferUShort) { + short[] data = dataBuffer instanceof DataBufferShort ? + ((DataBufferShort)dataBuffer).getData() : + ((DataBufferUShort)dataBuffer).getData(); + + for(int y = 0; y < rectHeight; y++) { + int xRemaining = rectWidth; + int i = eltOffset; + while(xRemaining > 8) { + short datum = data[i++]; + binaryDataArray[b++] = (byte)((datum >>> 8) & 0xFF); + binaryDataArray[b++] = (byte)(datum & 0xFF); + xRemaining -= 16; + } + if(xRemaining > 0) { + binaryDataArray[b++] = (byte)((data[i] >>> 8) & 0XFF); + } + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferInt) { + int[] data = ((DataBufferInt)dataBuffer).getData(); + + for(int y = 0; y < rectHeight; y++) { + int xRemaining = rectWidth; + int i = eltOffset; + while(xRemaining > 24) { + int datum = data[i++]; + binaryDataArray[b++] = (byte)((datum >>> 24) & 0xFF); + binaryDataArray[b++] = (byte)((datum >>> 16) & 0xFF); + binaryDataArray[b++] = (byte)((datum >>> 8) & 0xFF); + binaryDataArray[b++] = (byte)(datum & 0xFF); + xRemaining -= 32; + } + int shift = 24; + while(xRemaining > 0) { + binaryDataArray[b++] = + (byte)((data[i] >>> shift) & 0xFF); + shift -= 8; + xRemaining -= 8; + } + eltOffset += lineStride; + } + } + } else { // bitOffset != 0 + if(dataBuffer instanceof DataBufferByte) { + byte[] data = ((DataBufferByte)dataBuffer).getData(); + + if((bitOffset & 7) == 0) { + int stride = numBytesPerRow; + int offset = 0; + for(int y = 0; y < rectHeight; y++) { + System.arraycopy(data, eltOffset, + binaryDataArray, offset, + stride); + offset += stride; + eltOffset += lineStride; + } + } else { // bitOffset % 8 != 0 + int leftShift = bitOffset & 7; + int rightShift = 8 - leftShift; + for(int y = 0; y < rectHeight; y++) { + int i = eltOffset; + int xRemaining = rectWidth; + while(xRemaining > 0) { + if(xRemaining > rightShift) { + binaryDataArray[b++] = + (byte)(((data[i++]&0xFF) << leftShift) | + ((data[i]&0xFF) >>> rightShift)); + } else { + binaryDataArray[b++] = + (byte)((data[i]&0xFF) << leftShift); + } + xRemaining -= 8; + } + eltOffset += lineStride; + } + } + } else if(dataBuffer instanceof DataBufferShort || + dataBuffer instanceof DataBufferUShort) { + short[] data = dataBuffer instanceof DataBufferShort ? + ((DataBufferShort)dataBuffer).getData() : + ((DataBufferUShort)dataBuffer).getData(); + + for(int y = 0; y < rectHeight; y++) { + int bOffset = bitOffset; + for(int x = 0; x < rectWidth; x += 8, bOffset += 8) { + int i = eltOffset + bOffset/16; + int mod = bOffset % 16; + int left = data[i] & 0xFFFF; + if(mod <= 8) { + binaryDataArray[b++] = (byte)(left >>> (8 - mod)); + } else { + int delta = mod - 8; + int right = data[i+1] & 0xFFFF; + binaryDataArray[b++] = + (byte)((left << delta) | + (right >>> (16 - delta))); + } + } + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferInt) { + int[] data = ((DataBufferInt)dataBuffer).getData(); + + for(int y = 0; y < rectHeight; y++) { + int bOffset = bitOffset; + for(int x = 0; x < rectWidth; x += 8, bOffset += 8) { + int i = eltOffset + bOffset/32; + int mod = bOffset % 32; + int left = data[i]; + if(mod <= 24) { + binaryDataArray[b++] = + (byte)(left >>> (24 - mod)); + } else { + int delta = mod - 24; + int right = data[i+1]; + binaryDataArray[b++] = + (byte)((left << delta) | + (right >>> (32 - delta))); + } + } + eltOffset += lineStride; + } + } + } + + return binaryDataArray; + } + + /** + * Returns the binary data unpacked into an array of bytes. + * The line stride will be the width of the Raster. + * + * @throws IllegalArgumentException if isBinary() returns + * false with the SampleModel of the + * supplied Raster as argument. + */ + public static byte[] getUnpackedBinaryData(Raster raster, + Rectangle rect) { + SampleModel sm = raster.getSampleModel(); + if(!isBinary(sm)) { + throw new IllegalArgumentException(I18N.getString("ImageUtil0")); + } + + int rectX = rect.x; + int rectY = rect.y; + int rectWidth = rect.width; + int rectHeight = rect.height; + + DataBuffer dataBuffer = raster.getDataBuffer(); + + int dx = rectX - raster.getSampleModelTranslateX(); + int dy = rectY - raster.getSampleModelTranslateY(); + + MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm; + int lineStride = mpp.getScanlineStride(); + int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); + int bitOffset = mpp.getBitOffset(dx); + + byte[] bdata = new byte[rectWidth*rectHeight]; + int maxY = rectY + rectHeight; + int maxX = rectX + rectWidth; + int k = 0; + + if(dataBuffer instanceof DataBufferByte) { + byte[] data = ((DataBufferByte)dataBuffer).getData(); + for(int y = rectY; y < maxY; y++) { + int bOffset = eltOffset*8 + bitOffset; + for(int x = rectX; x < maxX; x++) { + byte b = data[bOffset/8]; + bdata[k++] = + (byte)((b >>> (7 - bOffset & 7)) & 0x0000001); + bOffset++; + } + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferShort || + dataBuffer instanceof DataBufferUShort) { + short[] data = dataBuffer instanceof DataBufferShort ? + ((DataBufferShort)dataBuffer).getData() : + ((DataBufferUShort)dataBuffer).getData(); + for(int y = rectY; y < maxY; y++) { + int bOffset = eltOffset*16 + bitOffset; + for(int x = rectX; x < maxX; x++) { + short s = data[bOffset/16]; + bdata[k++] = + (byte)((s >>> (15 - bOffset % 16)) & + 0x0000001); + bOffset++; + } + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferInt) { + int[] data = ((DataBufferInt)dataBuffer).getData(); + for(int y = rectY; y < maxY; y++) { + int bOffset = eltOffset*32 + bitOffset; + for(int x = rectX; x < maxX; x++) { + int i = data[bOffset/32]; + bdata[k++] = + (byte)((i >>> (31 - bOffset % 32)) & + 0x0000001); + bOffset++; + } + eltOffset += lineStride; + } + } + + return bdata; + } + + /** + * Sets the supplied Raster's data from an array + * of packed binary data of the form returned by + * getPackedBinaryData(). + * + * @throws IllegalArgumentException if isBinary() returns + * false with the SampleModel of the + * supplied Raster as argument. + */ + public static void setPackedBinaryData(byte[] binaryDataArray, + WritableRaster raster, + Rectangle rect) { + SampleModel sm = raster.getSampleModel(); + if(!isBinary(sm)) { + throw new IllegalArgumentException(I18N.getString("ImageUtil0")); + } + + int rectX = rect.x; + int rectY = rect.y; + int rectWidth = rect.width; + int rectHeight = rect.height; + + DataBuffer dataBuffer = raster.getDataBuffer(); + + int dx = rectX - raster.getSampleModelTranslateX(); + int dy = rectY - raster.getSampleModelTranslateY(); + + MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm; + int lineStride = mpp.getScanlineStride(); + int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); + int bitOffset = mpp.getBitOffset(dx); + + int b = 0; + + if(bitOffset == 0) { + if(dataBuffer instanceof DataBufferByte) { + byte[] data = ((DataBufferByte)dataBuffer).getData(); + if(data == binaryDataArray) { + // Optimal case: simply return. + return; + } + int stride = (rectWidth + 7)/8; + int offset = 0; + for(int y = 0; y < rectHeight; y++) { + System.arraycopy(binaryDataArray, offset, + data, eltOffset, + stride); + offset += stride; + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferShort || + dataBuffer instanceof DataBufferUShort) { + short[] data = dataBuffer instanceof DataBufferShort ? + ((DataBufferShort)dataBuffer).getData() : + ((DataBufferUShort)dataBuffer).getData(); + + for(int y = 0; y < rectHeight; y++) { + int xRemaining = rectWidth; + int i = eltOffset; + while(xRemaining > 8) { + data[i++] = + (short)(((binaryDataArray[b++] & 0xFF) << 8) | + (binaryDataArray[b++] & 0xFF)); + xRemaining -= 16; + } + if(xRemaining > 0) { + data[i++] = + (short)((binaryDataArray[b++] & 0xFF) << 8); + } + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferInt) { + int[] data = ((DataBufferInt)dataBuffer).getData(); + + for(int y = 0; y < rectHeight; y++) { + int xRemaining = rectWidth; + int i = eltOffset; + while(xRemaining > 24) { + data[i++] = + (int)(((binaryDataArray[b++] & 0xFF) << 24) | + ((binaryDataArray[b++] & 0xFF) << 16) | + ((binaryDataArray[b++] & 0xFF) << 8) | + (binaryDataArray[b++] & 0xFF)); + xRemaining -= 32; + } + int shift = 24; + while(xRemaining > 0) { + data[i] |= + (int)((binaryDataArray[b++] & 0xFF) << shift); + shift -= 8; + xRemaining -= 8; + } + eltOffset += lineStride; + } + } + } else { // bitOffset != 0 + int stride = (rectWidth + 7)/8; + int offset = 0; + if(dataBuffer instanceof DataBufferByte) { + byte[] data = ((DataBufferByte)dataBuffer).getData(); + + if((bitOffset & 7) == 0) { + for(int y = 0; y < rectHeight; y++) { + System.arraycopy(binaryDataArray, offset, + data, eltOffset, + stride); + offset += stride; + eltOffset += lineStride; + } + } else { // bitOffset % 8 != 0 + int rightShift = bitOffset & 7; + int leftShift = 8 - rightShift; + int leftShift8 = 8 + leftShift; + int mask = (byte)(255< 0) { + byte datum = binaryDataArray[b++]; + + if (xRemaining > leftShift8) { + // when all the bits in this BYTE will be set + // into the data buffer. + data[i] = (byte)((data[i] & mask ) | + ((datum&0xFF) >>> rightShift)); + data[++i] = (byte)((datum & 0xFF) << leftShift); + } else if (xRemaining > leftShift) { + // All the "leftShift" high bits will be set + // into the data buffer. But not all the + // "rightShift" low bits will be set. + data[i] = (byte)((data[i] & mask ) | + ((datum&0xFF) >>> rightShift)); + i++; + data[i] = + (byte)((data[i] & mask1) | ((datum & 0xFF) << leftShift)); + } + else { + // Less than "leftShift" high bits will be set. + int remainMask = (1 << leftShift - xRemaining) - 1; + data[i] = + (byte)((data[i] & (mask | remainMask)) | + (datum&0xFF) >>> rightShift & ~remainMask); + } + xRemaining -= 8; + } + eltOffset += lineStride; + } + } + } else if(dataBuffer instanceof DataBufferShort || + dataBuffer instanceof DataBufferUShort) { + short[] data = dataBuffer instanceof DataBufferShort ? + ((DataBufferShort)dataBuffer).getData() : + ((DataBufferUShort)dataBuffer).getData(); + + int rightShift = bitOffset & 7; + int leftShift = 8 - rightShift; + int leftShift16 = 16 + leftShift; + int mask = (short)(~(255 << leftShift)); + int mask1 = (short)(65535 << leftShift); + int mask2 = (short)~mask1; + + for(int y = 0; y < rectHeight; y++) { + int bOffset = bitOffset; + int xRemaining = rectWidth; + for(int x = 0; x < rectWidth; + x += 8, bOffset += 8, xRemaining -= 8) { + int i = eltOffset + (bOffset >> 4); + int mod = bOffset & 15; + int datum = binaryDataArray[b++] & 0xFF; + if(mod <= 8) { + // This BYTE is set into one SHORT + if (xRemaining < 8) { + // Mask the bits to be set. + datum &= 255 << 8 - xRemaining; + } + data[i] = (short)((data[i] & mask) | (datum << leftShift)); + } else if (xRemaining > leftShift16) { + // This BYTE will be set into two SHORTs + data[i] = (short)((data[i] & mask1) | ((datum >>> rightShift)&0xFFFF)); + data[++i] = + (short)((datum << leftShift)&0xFFFF); + } else if (xRemaining > leftShift) { + // This BYTE will be set into two SHORTs; + // But not all the low bits will be set into SHORT + data[i] = (short)((data[i] & mask1) | ((datum >>> rightShift)&0xFFFF)); + i++; + data[i] = + (short)((data[i] & mask2) | ((datum << leftShift)&0xFFFF)); + } else { + // Only some of the high bits will be set into + // SHORTs + int remainMask = (1 << leftShift - xRemaining) - 1; + data[i] = (short)((data[i] & (mask1 | remainMask)) | + ((datum >>> rightShift)&0xFFFF & ~remainMask)); + } + } + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferInt) { + int[] data = ((DataBufferInt)dataBuffer).getData(); + int rightShift = bitOffset & 7; + int leftShift = 8 - rightShift; + int leftShift32 = 32 + leftShift; + int mask = 0xFFFFFFFF << leftShift; + int mask1 = ~mask; + + for(int y = 0; y < rectHeight; y++) { + int bOffset = bitOffset; + int xRemaining = rectWidth; + for(int x = 0; x < rectWidth; + x += 8, bOffset += 8, xRemaining -= 8) { + int i = eltOffset + (bOffset >> 5); + int mod = bOffset & 31; + int datum = binaryDataArray[b++] & 0xFF; + if(mod <= 24) { + // This BYTE is set into one INT + int shift = 24 - mod; + if (xRemaining < 8) { + // Mask the bits to be set. + datum &= 255 << 8 - xRemaining; + } + data[i] = (data[i] & (~(255 << shift))) | (datum << shift); + } else if (xRemaining > leftShift32) { + // All the bits of this BYTE will be set into two INTs + data[i] = (data[i] & mask) | (datum >>> rightShift); + data[++i] = datum << leftShift; + } else if (xRemaining > leftShift) { + // This BYTE will be set into two INTs; + // But not all the low bits will be set into INT + data[i] = (data[i] & mask) | (datum >>> rightShift); + i++; + data[i] = (data[i] & mask1) | (datum << leftShift); + } else { + // Only some of the high bits will be set into INT + int remainMask = (1 << leftShift - xRemaining) - 1; + data[i] = (data[i] & (mask | remainMask)) | + (datum >>> rightShift & ~remainMask); + } + } + eltOffset += lineStride; + } + } + } + } + + /** + * Copies data into the packed array of the Raster + * from an array of unpacked data of the form returned by + * getUnpackedBinaryData(). + * + *

If the data are binary, then the target bit will be set if + * and only if the corresponding byte is non-zero. + * + * @throws IllegalArgumentException if isBinary() returns + * false with the SampleModel of the + * supplied Raster as argument. + */ + public static void setUnpackedBinaryData(byte[] bdata, + WritableRaster raster, + Rectangle rect) { + SampleModel sm = raster.getSampleModel(); + if(!isBinary(sm)) { + throw new IllegalArgumentException(I18N.getString("ImageUtil0")); + } + + int rectX = rect.x; + int rectY = rect.y; + int rectWidth = rect.width; + int rectHeight = rect.height; + + DataBuffer dataBuffer = raster.getDataBuffer(); + + int dx = rectX - raster.getSampleModelTranslateX(); + int dy = rectY - raster.getSampleModelTranslateY(); + + MultiPixelPackedSampleModel mpp = (MultiPixelPackedSampleModel)sm; + int lineStride = mpp.getScanlineStride(); + int eltOffset = dataBuffer.getOffset() + mpp.getOffset(dx, dy); + int bitOffset = mpp.getBitOffset(dx); + + int k = 0; + + if(dataBuffer instanceof DataBufferByte) { + byte[] data = ((DataBufferByte)dataBuffer).getData(); + for(int y = 0; y < rectHeight; y++) { + int bOffset = eltOffset*8 + bitOffset; + for(int x = 0; x < rectWidth; x++) { + if(bdata[k++] != (byte)0) { + data[bOffset/8] |= + (byte)(0x00000001 << (7 - bOffset & 7)); + } + bOffset++; + } + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferShort || + dataBuffer instanceof DataBufferUShort) { + short[] data = dataBuffer instanceof DataBufferShort ? + ((DataBufferShort)dataBuffer).getData() : + ((DataBufferUShort)dataBuffer).getData(); + for(int y = 0; y < rectHeight; y++) { + int bOffset = eltOffset*16 + bitOffset; + for(int x = 0; x < rectWidth; x++) { + if(bdata[k++] != (byte)0) { + data[bOffset/16] |= + (short)(0x00000001 << + (15 - bOffset % 16)); + } + bOffset++; + } + eltOffset += lineStride; + } + } else if(dataBuffer instanceof DataBufferInt) { + int[] data = ((DataBufferInt)dataBuffer).getData(); + for(int y = 0; y < rectHeight; y++) { + int bOffset = eltOffset*32 + bitOffset; + for(int x = 0; x < rectWidth; x++) { + if(bdata[k++] != (byte)0) { + data[bOffset/32] |= + (int)(0x00000001 << + (31 - bOffset % 32)); + } + bOffset++; + } + eltOffset += lineStride; + } + } + } + + public static boolean isBinary(SampleModel sm) { + return sm instanceof MultiPixelPackedSampleModel && + ((MultiPixelPackedSampleModel)sm).getPixelBitStride() == 1 && + sm.getNumBands() == 1; + } + + public static ColorModel createColorModel(ColorSpace colorSpace, + SampleModel sampleModel) { + ColorModel colorModel = null; + + if(sampleModel == null) { + throw new IllegalArgumentException(I18N.getString("ImageUtil1")); + } + + int numBands = sampleModel.getNumBands(); + if (numBands < 1 || numBands > 4) { + return null; + } + + int dataType = sampleModel.getDataType(); + if (sampleModel instanceof ComponentSampleModel) { + if (dataType < DataBuffer.TYPE_BYTE || + //dataType == DataBuffer.TYPE_SHORT || + dataType > DataBuffer.TYPE_DOUBLE) { + return null; + } + + if (colorSpace == null) + colorSpace = + numBands <= 2 ? + ColorSpace.getInstance(ColorSpace.CS_GRAY) : + ColorSpace.getInstance(ColorSpace.CS_sRGB); + + boolean useAlpha = (numBands == 2) || (numBands == 4); + int transparency = useAlpha ? + Transparency.TRANSLUCENT : Transparency.OPAQUE; + + boolean premultiplied = false; + + int dataTypeSize = DataBuffer.getDataTypeSize(dataType); + int[] bits = new int[numBands]; + for (int i = 0; i < numBands; i++) { + bits[i] = dataTypeSize; + } + + colorModel = new ComponentColorModel(colorSpace, + bits, + useAlpha, + premultiplied, + transparency, + dataType); + } else if (sampleModel instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = + (SinglePixelPackedSampleModel)sampleModel; + + int[] bitMasks = sppsm.getBitMasks(); + int rmask = 0; + int gmask = 0; + int bmask = 0; + int amask = 0; + + numBands = bitMasks.length; + if (numBands <= 2) { + rmask = gmask = bmask = bitMasks[0]; + if (numBands == 2) { + amask = bitMasks[1]; + } + } else { + rmask = bitMasks[0]; + gmask = bitMasks[1]; + bmask = bitMasks[2]; + if (numBands == 4) { + amask = bitMasks[3]; + } + } + + int[] sampleSize = sppsm.getSampleSize(); + int bits = 0; + for (int i = 0; i < sampleSize.length; i++) { + bits += sampleSize[i]; + } + + if (colorSpace == null) + colorSpace = ColorSpace.getInstance(ColorSpace.CS_sRGB); + + colorModel = + new DirectColorModel(colorSpace, + bits, rmask, gmask, bmask, amask, + false, + sampleModel.getDataType()); + } else if (sampleModel instanceof MultiPixelPackedSampleModel) { + int bits = + ((MultiPixelPackedSampleModel)sampleModel).getPixelBitStride(); + int size = 1 << bits; + byte[] comp = new byte[size]; + + for (int i = 0; i < size; i++) + comp[i] = (byte)(255 * i / (size - 1)); + + colorModel = new IndexColorModel(bits, size, comp, comp, comp); + } + + return colorModel; + } + + public static int getElementSize(SampleModel sm) { + int elementSize = DataBuffer.getDataTypeSize(sm.getDataType()); + + if (sm instanceof MultiPixelPackedSampleModel) { + MultiPixelPackedSampleModel mppsm = + (MultiPixelPackedSampleModel)sm; + return mppsm.getSampleSize(0) * mppsm.getNumBands(); + } else if (sm instanceof ComponentSampleModel) { + return sm.getNumBands() * elementSize; + } else if (sm instanceof SinglePixelPackedSampleModel) { + return elementSize; + } + + return elementSize * sm.getNumBands(); + + } + + public static long getTileSize(SampleModel sm) { + int elementSize = DataBuffer.getDataTypeSize(sm.getDataType()); + + if (sm instanceof MultiPixelPackedSampleModel) { + MultiPixelPackedSampleModel mppsm = + (MultiPixelPackedSampleModel)sm; + return (mppsm.getScanlineStride() * mppsm.getHeight() + + (mppsm.getDataBitOffset() + elementSize -1) / elementSize) * + ((elementSize + 7) / 8); + } else if (sm instanceof ComponentSampleModel) { + ComponentSampleModel csm = (ComponentSampleModel)sm; + int[] bandOffsets = csm.getBandOffsets(); + int maxBandOff = bandOffsets[0]; + for (int i=1; i= 0) + size += maxBandOff + 1; + if (pixelStride > 0) + size += pixelStride * (sm.getWidth() - 1); + if (scanlineStride > 0) + size += scanlineStride * (sm.getHeight() - 1); + + int[] bankIndices = csm.getBankIndices(); + maxBandOff = bankIndices[0]; + for (int i=1; i 0) + size += pixelStride * (sm.getWidth() - 1); + if (scanlineStride > 0) + size += scanlineStride * (sm.getHeight() - 1); + return size * ((elementSize + 7) / 8); + } else + return getTileSize(sm); + } + /** + * Tests whether the color indices represent a gray-scale image. + * + * @param r The red channel color indices. + * @param g The green channel color indices. + * @param b The blue channel color indices. + * @return If all the indices have 256 entries, and are identical mappings, + * return true; otherwise, return false. + */ + public static boolean isIndicesForGrayscale(byte[] r, byte[] g, byte[] b) { + if (r.length != g.length || r.length != b.length) + return false; + + int size = r.length; + + if (size != 256) + return false; + + for (int i = 0; i < size; i++) { + byte temp = (byte) i; + + if (r[i] != temp || g[i] != temp || b[i] != temp) + return false; + } + + return true; + } + + /** Converts the provided object to String */ + public static String convertObjectToString(Object obj) { + if (obj == null) + return ""; + + String s = ""; + if (obj instanceof byte[]) { + byte[] bArray = (byte[])obj; + for (int i = 0; i < bArray.length; i++) + s += bArray[i] + " "; + return s; + } + + if (obj instanceof int[]) { + int[] iArray = (int[])obj; + for (int i = 0; i < iArray.length; i++) + s += iArray[i] + " " ; + return s; + } + + if (obj instanceof short[]) { + short[] sArray = (short[])obj; + for (int i = 0; i < sArray.length; i++) + s += sArray[i] + " " ; + return s; + } + + return obj.toString(); + + } + + /** Checks that the provided ImageWriter can encode + * the provided ImageTypeSpecifier or not. If not, an + * IIOException will be thrown. + * @param writer The provided ImageWriter. + * @param type The image to be tested. + * @throws IIOException If the writer cannot encoded the provided image. + */ + public static final void canEncodeImage(ImageWriter writer, + ImageTypeSpecifier type) + throws IIOException { + ImageWriterSpi spi = writer.getOriginatingProvider(); + + if(type != null && spi != null && !spi.canEncodeImage(type)) { + throw new IIOException(I18N.getString("ImageUtil2")+" "+ + writer.getClass().getName()); + } + } + + /** Checks that the provided ImageWriter can encode + * the provided ColorModel and SampleModel. + * If not, an IIOException will be thrown. + * @param writer The provided ImageWriter. + * @param colorModel The provided ColorModel. + * @param sampleModel The provided SampleModel. + * @throws IIOException If the writer cannot encoded the provided image. + */ + public static final void canEncodeImage(ImageWriter writer, + ColorModel colorModel, + SampleModel sampleModel) + throws IIOException { + ImageTypeSpecifier type = null; + if (colorModel != null && sampleModel != null) + type = new ImageTypeSpecifier(colorModel, sampleModel); + canEncodeImage(writer, type); + } + + /** + * Returns whether the image has contiguous data across rows. + */ + public static final boolean imageIsContiguous(RenderedImage image) { + SampleModel sm; + if(image instanceof BufferedImage) { + WritableRaster ras = ((BufferedImage)image).getRaster(); + sm = ras.getSampleModel(); + } else { + sm = image.getSampleModel(); + } + + if (sm instanceof ComponentSampleModel) { + // Ensure image rows samples are stored contiguously + // in a single bank. + ComponentSampleModel csm = (ComponentSampleModel)sm; + + if (csm.getPixelStride() != csm.getNumBands()) { + return false; + } + + int[] bandOffsets = csm.getBandOffsets(); + for (int i = 0; i < bandOffsets.length; i++) { + if (bandOffsets[i] != i) { + return false; + } + } + + int[] bankIndices = csm.getBankIndices(); + for (int i = 0; i < bandOffsets.length; i++) { + if (bankIndices[i] != 0) { + return false; + } + } + + return true; + } + + // Otherwise true if and only if it's a bilevel image with + // a MultiPixelPackedSampleModel, 1 bit per pixel, and 1 bit + // pixel stride. + return ImageUtil.isBinary(sm); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/InputStreamAdapter.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/InputStreamAdapter.java new file mode 100644 index 000000000..7f5c2c27f --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/InputStreamAdapter.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.io.IOException; +import java.io.InputStream; +import javax.imageio.stream.ImageInputStream; + +public class InputStreamAdapter extends InputStream { + + ImageInputStream stream; + + public InputStreamAdapter(ImageInputStream stream) { + super(); + + this.stream = stream; + } + + public int read() throws IOException { + return stream.read(); + } + + public int read(byte b[], int off, int len) throws IOException { + return stream.read(b, off, len); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/LZWCompressor.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/LZWCompressor.java new file mode 100644 index 000000000..0177848ad --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/LZWCompressor.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.io.IOException; +import java.io.PrintStream; +import javax.imageio.stream.ImageOutputStream; + +/* + * Modified from original LZWCompressor to change interface to passing a + * buffer of data to be compressed. + */ +public class LZWCompressor { + /** base underlying code size of data being compressed 8 for TIFF, 1 to 8 for GIF **/ + int codeSize; + + /** reserved clear code based on code size **/ + int clearCode; + + /** reserved end of data code based on code size **/ + int endOfInfo; + + /** current number bits output for each code **/ + int numBits; + + /** limit at which current number of bits code size has to be increased **/ + int limit; + + /** the prefix code which represents the predecessor string to current input point **/ + short prefix; + + /** output destination for bit codes **/ + BitFile bf; + + /** general purpose LZW string table **/ + LZWStringTable lzss; + + /** modify the limits of the code values in LZW encoding due to TIFF bug / feature **/ + boolean tiffFudge; + + /** + * @param out destination for compressed data + * @param codeSize the initial code size for the LZW compressor + * @param TIFF flag indicating that TIFF lzw fudge needs to be applied + * @exception IOException if underlying output stream error + **/ + public LZWCompressor(ImageOutputStream out, int codeSize, boolean TIFF) + throws IOException + { + bf = new BitFile(out, !TIFF); // set flag for GIF as NOT tiff + this.codeSize = codeSize; + tiffFudge = TIFF; + clearCode = 1 << codeSize; + endOfInfo = clearCode + 1; + numBits = codeSize + 1; + + limit = (1 << numBits) - 1; + if (tiffFudge) { + --limit; + } + + prefix = (short)0xFFFF; + lzss = new LZWStringTable(); + lzss.clearTable(codeSize); + bf.writeBits(clearCode, numBits); + } + + /** + * @param buf data to be compressed to output stream + * @exception IOException if underlying output stream error + **/ + public void compress(byte[] buf, int offset, int length) + throws IOException + { + int idx; + byte c; + short index; + + int maxOffset = offset + length; + for (idx = offset; idx < maxOffset; ++idx) { + c = buf[idx]; + if ((index = lzss.findCharString(prefix, c)) != -1) { + prefix = index; + } else { + bf.writeBits(prefix, numBits); + if (lzss.addCharString(prefix, c) > limit) { + if (numBits == 12) { + bf.writeBits(clearCode, numBits); + lzss.clearTable(codeSize); + numBits = codeSize + 1; + } else { + ++numBits; + } + + limit = (1 << numBits) - 1; + if (tiffFudge) { + --limit; + } + } + prefix = (short)((short)c & 0xFF); + } + } + } + + /* + * Indicate to compressor that no more data to go so write out + * any remaining buffered data. + * + * @exception IOException if underlying output stream error + */ + public void flush() throws IOException { + if (prefix != -1) { + bf.writeBits(prefix, numBits); + } + + bf.writeBits(endOfInfo, numBits); + bf.flush(); + } + + public void dump(PrintStream out) { + lzss.dump(out); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/LZWStringTable.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/LZWStringTable.java new file mode 100644 index 000000000..cfed0f43c --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/LZWStringTable.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.io.PrintStream; + +/** + * General purpose LZW String Table. + * Extracted from GIFEncoder by Adam Doppelt + * Comments added by Robin Luiten + * expandCode added by Robin Luiten + * The strLen table to give quick access to the lenght of an expanded + * code for use by the expandCode method added by Robin. + **/ +public class LZWStringTable { + /** codesize + Reserved Codes */ + private final static int RES_CODES = 2; + + private final static short HASH_FREE = (short)0xFFFF; + private final static short NEXT_FIRST = (short)0xFFFF; + + private final static int MAXBITS = 12; + private final static int MAXSTR = (1 << MAXBITS); + + private final static short HASHSIZE = 9973; + private final static short HASHSTEP = 2039; + + byte[] strChr; // after predecessor character + short[] strNxt; // predecessor string + short[] strHsh; // hash table to find predecessor + char pairs + short numStrings; // next code if adding new prestring + char + + /* + * each entry corresponds to a code and contains the length of data + * that the code expands to when decoded. + */ + int[] strLen; + + /* + * Constructor allocate memory for string store data + */ + public LZWStringTable() { + strChr = new byte[MAXSTR]; + strNxt = new short[MAXSTR]; + strLen = new int[MAXSTR]; + strHsh = new short[HASHSIZE]; + } + + /* + * @param index value of -1 indicates no predecessor [used in initialisation] + * @param b the byte [character] to add to the string store which follows + * the predecessor string specified the index. + * @return 0xFFFF if no space in table left for addition of predecesor + * index and byte b. Else return the code allocated for combination index + b. + */ + public int addCharString(short index, byte b) { + int hshidx; + + if (numStrings >= MAXSTR) { // if used up all codes + return 0xFFFF; + } + + hshidx = hash(index, b); + while (strHsh[hshidx] != HASH_FREE) { + hshidx = (hshidx + HASHSTEP) % HASHSIZE; + } + + strHsh[hshidx] = numStrings; + strChr[numStrings] = b; + if (index == HASH_FREE) { + strNxt[numStrings] = NEXT_FIRST; + strLen[numStrings] = 1; + } else { + strNxt[numStrings] = index; + strLen[numStrings] = strLen[index] + 1; + } + + return numStrings++; // return the code and inc for next code + } + + /* + * @param index index to prefix string + * @param b the character that follws the index prefix + * @return b if param index is HASH_FREE. Else return the code + * for this prefix and byte successor + */ + public short findCharString(short index, byte b) { + int hshidx, nxtidx; + + if (index == HASH_FREE) { + return (short)(b & 0xFF); // Rob fixed used to sign extend + } + + hshidx = hash(index, b); + while ((nxtidx = strHsh[hshidx]) != HASH_FREE) { // search + if (strNxt[nxtidx] == index && strChr[nxtidx] == b) { + return (short)nxtidx; + } + hshidx = (hshidx + HASHSTEP) % HASHSIZE; + } + + return (short)0xFFFF; + } + + /* + * @param codesize the size of code to be preallocated for the + * string store. + */ + public void clearTable(int codesize) { + numStrings = 0; + + for (int q = 0; q < HASHSIZE; q++) { + strHsh[q] = HASH_FREE; + } + + int w = (1 << codesize) + RES_CODES; + for (int q = 0; q < w; q++) { + addCharString((short)0xFFFF, (byte)q); // init with no prefix + } + } + + static public int hash(short index, byte lastbyte) { + return ((int)((short)(lastbyte << 8) ^ index) & 0xFFFF) % HASHSIZE; + } + + /* + * If expanded data doesn't fit into array only what will fit is written + * to buf and the return value indicates how much of the expanded code has + * been written to the buf. The next call to expandCode() should be with + * the same code and have the skip parameter set the negated value of the + * previous return. Succesive negative return values should be negated and + * added together for next skip parameter value with same code. + * + * @param buf buffer to place expanded data into + * @param offset offset to place expanded data + * @param code the code to expand to the byte array it represents. + * PRECONDITION This code must already be in the LZSS + * @param skipHead is the number of bytes at the start of the expanded code to + * be skipped before data is written to buf. It is possible that skipHead is + * equal to codeLen. + * @return the length of data expanded into buf. If the expanded code is longer + * than space left in buf then the value returned is a negative number which when + * negated is equal to the number of bytes that were used of the code being expanded. + * This negative value also indicates the buffer is full. + */ + public int expandCode(byte[] buf, int offset, short code, int skipHead) { + if (offset == -2) { + if (skipHead == 1) { + skipHead = 0; + } + } + if (code == (short)0xFFFF || // just in case + skipHead == strLen[code]) // DONE no more unpacked + { + return 0; + } + + int expandLen; // how much data we are actually expanding + int codeLen = strLen[code] - skipHead; // length of expanded code left + int bufSpace = buf.length - offset; // how much space left + if (bufSpace > codeLen) { + expandLen = codeLen; // only got this many to unpack + } else { + expandLen = bufSpace; + } + + int skipTail = codeLen - expandLen; // only > 0 if codeLen > bufSpace [left overs] + + int idx = offset + expandLen; // initialise to exclusive end address of buffer area + + // NOTE: data unpacks in reverse direction and we are placing the + // unpacked data directly into the array in the correct location. + while ((idx > offset) && (code != (short)0xFFFF)) { + if (--skipTail < 0) { // skip required of expanded data + buf[--idx] = strChr[code]; + } + code = strNxt[code]; // to predecessor code + } + + if (codeLen > expandLen) { + return -expandLen; // indicate what part of codeLen used + } else { + return expandLen; // indicate length of dat unpacked + } + } + + public void dump(PrintStream out) { + int i; + for (i = 258; i < numStrings; ++i) { + out.println(" strNxt[" + i + "] = " + strNxt[i] + + " strChr " + Integer.toHexString(strChr[i] & 0xFF) + + " strLen " + Integer.toHexString(strLen[i])); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/PaletteBuilder.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/PaletteBuilder.java new file mode 100644 index 000000000..35506ecdc --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/PaletteBuilder.java @@ -0,0 +1,486 @@ +/* + * Copyright (c) 2005, 2006, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.awt.Transparency; +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.Color; +import javax.imageio.ImageTypeSpecifier; + + +/** + * This class implements the octree quantization method + * as it is described in the "Graphics Gems" + * (ISBN 0-12-286166-3, Chapter 4, pages 297-293) + */ +public class PaletteBuilder { + + /** + * maximum of tree depth + */ + protected static final int MAXLEVEL = 8; + + protected RenderedImage src; + protected ColorModel srcColorModel; + protected Raster srcRaster; + + protected int requiredSize; + + protected ColorNode root; + + protected int numNodes; + protected int maxNodes; + protected int currLevel; + protected int currSize; + + protected ColorNode[] reduceList; + protected ColorNode[] palette; + + protected int transparency; + protected ColorNode transColor; + + + /** + * Creates an image representing given image + * src using IndexColorModel. + * + * Lossless conversion is not always possible (e.g. if number + * of colors in the given image exceeds maximum palette size). + * Result image then is an approximation constructed by octree + * quantization method. + * + * @exception IllegalArgumentException if src is + * null. + * + * @exception UnsupportedOperationException if implemented method + * is unable to create approximation of src + * and canCreatePalette returns false. + * + * @see createIndexColorModel + * + * @see canCreatePalette + * + */ + public static RenderedImage createIndexedImage(RenderedImage src) { + PaletteBuilder pb = new PaletteBuilder(src); + pb.buildPalette(); + return pb.getIndexedImage(); + } + + /** + * Creates an palette representing colors from given image + * img. If number of colors in the given image exceeds + * maximum palette size closest colors would be merged. + * + * @exception IllegalArgumentException if img is + * null. + * + * @exception UnsupportedOperationException if implemented method + * is unable to create approximation of img + * and canCreatePalette returns false. + * + * @see createIndexedImage + * + * @see canCreatePalette + * + */ + public static IndexColorModel createIndexColorModel(RenderedImage img) { + PaletteBuilder pb = new PaletteBuilder(img); + pb.buildPalette(); + return pb.getIndexColorModel(); + } + + /** + * Returns true if PaletteBuilder is able to create + * palette for given image type. + * + * @param type an instance of ImageTypeSpecifier to be + * indexed. + * + * @return true if the PaletteBuilder + * is likely to be able to create palette for this image type. + * + * @exception IllegalArgumentException if type + * is null. + */ + public static boolean canCreatePalette(ImageTypeSpecifier type) { + if (type == null) { + throw new IllegalArgumentException("type == null"); + } + return true; + } + + /** + * Returns true if PaletteBuilder is able to create + * palette for given rendered image. + * + * @param image an instance of RenderedImage to be + * indexed. + * + * @return true if the PaletteBuilder + * is likely to be able to create palette for this image type. + * + * @exception IllegalArgumentException if image + * is null. + */ + public static boolean canCreatePalette(RenderedImage image) { + if (image == null) { + throw new IllegalArgumentException("image == null"); + } + ImageTypeSpecifier type = new ImageTypeSpecifier(image); + return canCreatePalette(type); + } + + protected RenderedImage getIndexedImage() { + IndexColorModel icm = getIndexColorModel(); + + BufferedImage dst = + new BufferedImage(src.getWidth(), src.getHeight(), + BufferedImage.TYPE_BYTE_INDEXED, icm); + + WritableRaster wr = dst.getRaster(); + for (int y =0; y < dst.getHeight(); y++) { + for (int x = 0; x < dst.getWidth(); x++) { + Color aColor = getSrcColor(x,y); + wr.setSample(x, y, 0, findColorIndex(root, aColor)); + } + } + + return dst; + } + + + protected PaletteBuilder(RenderedImage src) { + this(src, 256); + } + + protected PaletteBuilder(RenderedImage src, int size) { + this.src = src; + this.srcColorModel = src.getColorModel(); + this.srcRaster = src.getData(); + + this.transparency = + srcColorModel.getTransparency(); + + this.requiredSize = size; + } + + private Color getSrcColor(int x, int y) { + int argb = srcColorModel.getRGB(srcRaster.getDataElements(x, y, null)); + return new Color(argb, transparency != Transparency.OPAQUE); + } + + protected int findColorIndex(ColorNode aNode, Color aColor) { + if (transparency != Transparency.OPAQUE && + aColor.getAlpha() != 0xff) + { + return 0; // default transparnt pixel + } + + if (aNode.isLeaf) { + return aNode.paletteIndex; + } else { + int childIndex = getBranchIndex(aColor, aNode.level); + + return findColorIndex(aNode.children[childIndex], aColor); + } + } + + protected void buildPalette() { + reduceList = new ColorNode[MAXLEVEL + 1]; + for (int i = 0; i < reduceList.length; i++) { + reduceList[i] = null; + } + + numNodes = 0; + maxNodes = 0; + root = null; + currSize = 0; + currLevel = MAXLEVEL; + + /* + from the book + + */ + + int w = src.getWidth(); + int h = src.getHeight(); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + + Color aColor = getSrcColor(w - x - 1, h - y - 1); + /* + * If transparency of given image is not opaque we assume all + * colors with alpha less than 1.0 as fully transparent. + */ + if (transparency != Transparency.OPAQUE && + aColor.getAlpha() != 0xff) + { + if (transColor == null) { + this.requiredSize --; // one slot for transparent color + + transColor = new ColorNode(); + transColor.isLeaf = true; + } + transColor = insertNode(transColor, aColor, 0); + } else { + root = insertNode(root, aColor, 0); + } + if (currSize > requiredSize) { + reduceTree(); + } + } + } + } + + protected ColorNode insertNode(ColorNode aNode, Color aColor, int aLevel) { + + if (aNode == null) { + aNode = new ColorNode(); + numNodes++; + if (numNodes > maxNodes) { + maxNodes = numNodes; + } + aNode.level = aLevel; + aNode.isLeaf = (aLevel > MAXLEVEL); + if (aNode.isLeaf) { + currSize++; + } + } + aNode.colorCount++; + aNode.red += aColor.getRed(); + aNode.green += aColor.getGreen(); + aNode.blue += aColor.getBlue(); + + if (!aNode.isLeaf) { + int branchIndex = getBranchIndex(aColor, aLevel); + if (aNode.children[branchIndex] == null) { + aNode.childCount++; + if (aNode.childCount == 2) { + aNode.nextReducible = reduceList[aLevel]; + reduceList[aLevel] = aNode; + } + } + aNode.children[branchIndex] = + insertNode(aNode.children[branchIndex], aColor, aLevel + 1); + } + return aNode; + } + + protected IndexColorModel getIndexColorModel() { + int size = currSize; + if (transColor != null) { + size ++; // we need place for transparent color; + } + + byte[] red = new byte[size]; + byte[] green = new byte[size]; + byte[] blue = new byte[size]; + + int index = 0; + palette = new ColorNode[size]; + if (transColor != null) { + index ++; + } + + if (root != null) { + findPaletteEntry(root, index, red, green, blue); + } + + IndexColorModel icm = null; + if (transColor != null) { + icm = new IndexColorModel(8, size, red, green, blue, 0); + } else { + icm = new IndexColorModel(8, currSize, red, green, blue); + } + return icm; + } + + protected int findPaletteEntry(ColorNode aNode, int index, + byte[] red, byte[] green, byte[] blue) + { + if (aNode.isLeaf) { + red[index] = (byte)(aNode.red/aNode.colorCount); + green[index] = (byte)(aNode.green/aNode.colorCount); + blue[index] = (byte)(aNode.blue/aNode.colorCount); + aNode.paletteIndex = index; + + palette[index] = aNode; + + index++; + } else { + for (int i = 0; i < 8; i++) { + if (aNode.children[i] != null) { + index = findPaletteEntry(aNode.children[i], index, + red, green, blue); + } + } + } + return index; + } + + protected int getBranchIndex(Color aColor, int aLevel) { + if (aLevel > MAXLEVEL || aLevel < 0) { + throw new IllegalArgumentException("Invalid octree node depth: " + + aLevel); + } + + int shift = MAXLEVEL - aLevel; + int red_index = 0x1 & ((0xff & aColor.getRed()) >> shift); + int green_index = 0x1 & ((0xff & aColor.getGreen()) >> shift); + int blue_index = 0x1 & ((0xff & aColor.getBlue()) >> shift); + int index = (red_index << 2) | (green_index << 1) | blue_index; + return index; + } + + protected void reduceTree() { + int level = reduceList.length - 1; + while (reduceList[level] == null && level >= 0) { + level--; + } + + ColorNode thisNode = reduceList[level]; + if (thisNode == null) { + // nothing to reduce + return; + } + + // look for element with lower color count + ColorNode pList = thisNode; + int minColorCount = pList.colorCount; + + int cnt = 1; + while (pList.nextReducible != null) { + if (minColorCount > pList.nextReducible.colorCount) { + thisNode = pList; + minColorCount = pList.colorCount; + } + pList = pList.nextReducible; + cnt++; + } + + // save pointer to first reducible node + // NB: current color count for node could be changed in future + if (thisNode == reduceList[level]) { + reduceList[level] = thisNode.nextReducible; + } else { + pList = thisNode.nextReducible; // we need to process it + thisNode.nextReducible = pList.nextReducible; + thisNode = pList; + } + + if (thisNode.isLeaf) { + return; + } + + // reduce node + int leafChildCount = thisNode.getLeafChildCount(); + thisNode.isLeaf = true; + currSize -= (leafChildCount - 1); + int aDepth = thisNode.level; + for (int i = 0; i < 8; i++) { + thisNode.children[i] = freeTree(thisNode.children[i]); + } + thisNode.childCount = 0; + } + + protected ColorNode freeTree(ColorNode aNode) { + if (aNode == null) { + return null; + } + for (int i = 0; i < 8; i++) { + aNode.children[i] = freeTree(aNode.children[i]); + } + + numNodes--; + return null; + } + + /** + * The node of color tree. + */ + protected class ColorNode { + public boolean isLeaf; + public int childCount; + ColorNode[] children; + + public int colorCount; + public long red; + public long blue; + public long green; + + public int paletteIndex; + + public int level; + ColorNode nextReducible; + + public ColorNode() { + isLeaf = false; + level = 0; + childCount = 0; + children = new ColorNode[8]; + for (int i = 0; i < 8; i++) { + children[i] = null; + } + + colorCount = 0; + red = green = blue = 0; + + paletteIndex = 0; + } + + public int getLeafChildCount() { + if (isLeaf) { + return 0; + } + int cnt = 0; + for (int i = 0; i < children.length; i++) { + if (children[i] != null) { + if (children[i].isLeaf) { + cnt ++; + } else { + cnt += children[i].getLeafChildCount(); + } + } + } + return cnt; + } + + public int getRGB() { + int r = (int)red/colorCount; + int g = (int)green/colorCount; + int b = (int)blue/colorCount; + + int c = 0xff << 24 | (0xff&r) << 16 | (0xff&g) << 8 | (0xff&b); + return c; + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/ReaderUtil.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/ReaderUtil.java new file mode 100644 index 000000000..cedc428c3 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/ReaderUtil.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.awt.Point; +import java.awt.Rectangle; +import java.io.IOException; +import javax.imageio.stream.ImageInputStream; + +/** + * This class contains utility methods that may be useful to ImageReader + * plugins. Ideally these methods would be in the ImageReader base class + * so that all subclasses could benefit from them, but that would be an + * addition to the existing API, and it is not yet clear whether these methods + * are universally useful, so for now we will leave them here. + */ +public class ReaderUtil { + + // Helper for computeUpdatedPixels method + private static void computeUpdatedPixels(int sourceOffset, + int sourceExtent, + int destinationOffset, + int dstMin, + int dstMax, + int sourceSubsampling, + int passStart, + int passExtent, + int passPeriod, + int[] vals, + int offset) + { + // We need to satisfy the congruences: + // dst = destinationOffset + (src - sourceOffset)/sourceSubsampling + // + // src - passStart == 0 (mod passPeriod) + // src - sourceOffset == 0 (mod sourceSubsampling) + // + // subject to the inequalities: + // + // src >= passStart + // src < passStart + passExtent + // src >= sourceOffset + // src < sourceOffset + sourceExtent + // dst >= dstMin + // dst <= dstmax + // + // where + // + // dst = destinationOffset + (src - sourceOffset)/sourceSubsampling + // + // For now we use a brute-force approach although we could + // attempt to analyze the congruences. If passPeriod and + // sourceSubsamling are relatively prime, the period will be + // their product. If they share a common factor, either the + // period will be equal to the larger value, or the sequences + // will be completely disjoint, depending on the relationship + // between passStart and sourceOffset. Since we only have to do this + // twice per image (once each for X and Y), it seems cheap enough + // to do it the straightforward way. + + boolean gotPixel = false; + int firstDst = -1; + int secondDst = -1; + int lastDst = -1; + + for (int i = 0; i < passExtent; i++) { + int src = passStart + i*passPeriod; + if (src < sourceOffset) { + continue; + } + if ((src - sourceOffset) % sourceSubsampling != 0) { + continue; + } + if (src >= sourceOffset + sourceExtent) { + break; + } + + int dst = destinationOffset + + (src - sourceOffset)/sourceSubsampling; + if (dst < dstMin) { + continue; + } + if (dst > dstMax) { + break; + } + + if (!gotPixel) { + firstDst = dst; // Record smallest valid pixel + gotPixel = true; + } else if (secondDst == -1) { + secondDst = dst; // Record second smallest valid pixel + } + lastDst = dst; // Record largest valid pixel + } + + vals[offset] = firstDst; + + // If we never saw a valid pixel, set width to 0 + if (!gotPixel) { + vals[offset + 2] = 0; + } else { + vals[offset + 2] = lastDst - firstDst + 1; + } + + // The period is given by the difference of any two adjacent pixels + vals[offset + 4] = Math.max(secondDst - firstDst, 1); + } + + /** + * A utility method that computes the exact set of destination + * pixels that will be written during a particular decoding pass. + * The intent is to simplify the work done by readers in combining + * the source region, source subsampling, and destination offset + * information obtained from the ImageReadParam with + * the offsets and periods of a progressive or interlaced decoding + * pass. + * + * @param sourceRegion a Rectangle containing the + * source region being read, offset by the source subsampling + * offsets, and clipped against the source bounds, as returned by + * the getSourceRegion method. + * @param destinationOffset a Point containing the + * coordinates of the upper-left pixel to be written in the + * destination. + * @param dstMinX the smallest X coordinate (inclusive) of the + * destination Raster. + * @param dstMinY the smallest Y coordinate (inclusive) of the + * destination Raster. + * @param dstMaxX the largest X coordinate (inclusive) of the destination + * Raster. + * @param dstMaxY the largest Y coordinate (inclusive) of the destination + * Raster. + * @param sourceXSubsampling the X subsampling factor. + * @param sourceYSubsampling the Y subsampling factor. + * @param passXStart the smallest source X coordinate (inclusive) + * of the current progressive pass. + * @param passYStart the smallest source Y coordinate (inclusive) + * of the current progressive pass. + * @param passWidth the width in pixels of the current progressive + * pass. + * @param passHeight the height in pixels of the current progressive + * pass. + * @param passPeriodX the X period (horizontal spacing between + * pixels) of the current progressive pass. + * @param passPeriodY the Y period (vertical spacing between + * pixels) of the current progressive pass. + * + * @return an array of 6 ints containing the + * destination min X, min Y, width, height, X period and Y period + * of the region that will be updated. + */ + public static int[] computeUpdatedPixels(Rectangle sourceRegion, + Point destinationOffset, + int dstMinX, + int dstMinY, + int dstMaxX, + int dstMaxY, + int sourceXSubsampling, + int sourceYSubsampling, + int passXStart, + int passYStart, + int passWidth, + int passHeight, + int passPeriodX, + int passPeriodY) + { + int[] vals = new int[6]; + computeUpdatedPixels(sourceRegion.x, sourceRegion.width, + destinationOffset.x, + dstMinX, dstMaxX, sourceXSubsampling, + passXStart, passWidth, passPeriodX, + vals, 0); + computeUpdatedPixels(sourceRegion.y, sourceRegion.height, + destinationOffset.y, + dstMinY, dstMaxY, sourceYSubsampling, + passYStart, passHeight, passPeriodY, + vals, 1); + return vals; + } + + public static int readMultiByteInteger(ImageInputStream iis) + throws IOException + { + int value = iis.readByte(); + int result = value & 0x7f; + while((value & 0x80) == 0x80) { + result <<= 7; + value = iis.readByte(); + result |= (value & 0x7f); + } + return result; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/StandardMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/StandardMetadataFormat.java new file mode 100644 index 000000000..37c5b3fcb --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/StandardMetadataFormat.java @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.util.ArrayList; +import java.util.List; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class StandardMetadataFormat extends IIOMetadataFormatImpl { + + // Utility method for nodes with a single attribute named "value" + private void addSingleAttributeElement(String elementName, + String parentName, + int dataType) { + addElement(elementName, parentName, CHILD_POLICY_EMPTY); + addAttribute(elementName, "value", dataType, true, null); + } + + public StandardMetadataFormat() { + super(standardMetadataFormatName, CHILD_POLICY_SOME); + List values; + + // root -> Chroma + addElement("Chroma", standardMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> Chroma -> ColorSpaceType + addElement("ColorSpaceType", "Chroma", + CHILD_POLICY_EMPTY); + + values = new ArrayList(); + values.add("XYZ"); + values.add("Lab"); + values.add("Luv"); + values.add("YCbCr"); + values.add("Yxy"); + values.add("YCCK"); + values.add("PhotoYCC"); + values.add("RGB"); + values.add("GRAY"); + values.add("HSV"); + values.add("HLS"); + values.add("CMYK"); + values.add("CMY"); + values.add("2CLR"); + values.add("3CLR"); + values.add("4CLR"); + values.add("5CLR"); + values.add("6CLR"); + values.add("7CLR"); + values.add("8CLR"); + values.add("9CLR"); + values.add("ACLR"); + values.add("BCLR"); + values.add("CCLR"); + values.add("DCLR"); + values.add("ECLR"); + values.add("FCLR"); + addAttribute("ColorSpaceType", + "name", + DATATYPE_STRING, + true, + null, + values); + + // root -> Chroma -> NumChannels + addElement("NumChannels", "Chroma", + CHILD_POLICY_EMPTY); + addAttribute("NumChannels", "value", + DATATYPE_INTEGER, + true, + 0, Integer.MAX_VALUE); + + // root -> Chroma -> Gamma + addElement("Gamma", "Chroma", CHILD_POLICY_EMPTY); + addAttribute("Gamma", "value", + DATATYPE_FLOAT, true, null); + + // root -> Chroma -> BlackIsZero + addElement("BlackIsZero", "Chroma", CHILD_POLICY_EMPTY); + addBooleanAttribute("BlackIsZero", "value", true, true); + + // root -> Chroma -> Palette + addElement("Palette", "Chroma", 0, Integer.MAX_VALUE); + + // root -> Chroma -> PaletteEntry + addElement("PaletteEntry", "Palette", CHILD_POLICY_EMPTY); + addAttribute("PaletteEntry", "index", DATATYPE_INTEGER, + true, null); + addAttribute("PaletteEntry", "red", DATATYPE_INTEGER, + true, null); + addAttribute("PaletteEntry", "green", DATATYPE_INTEGER, + true, null); + addAttribute("PaletteEntry", "blue", DATATYPE_INTEGER, + true, null); + addAttribute("PaletteEntry", "alpha", DATATYPE_INTEGER, + false, "255"); + + // root -> Chroma -> BackgroundIndex + addElement("BackgroundIndex", "Chroma", CHILD_POLICY_EMPTY); + addAttribute("BackgroundIndex", "value", DATATYPE_INTEGER, + true, null); + + // root -> Chroma -> BackgroundColor + addElement("BackgroundColor", "Chroma", CHILD_POLICY_EMPTY); + addAttribute("BackgroundColor", "red", DATATYPE_INTEGER, + true, null); + addAttribute("BackgroundColor", "green", DATATYPE_INTEGER, + true, null); + addAttribute("BackgroundColor", "blue", DATATYPE_INTEGER, + true, null); + + // root -> Compression + addElement("Compression", standardMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> Compression -> CompressionTypeName + addSingleAttributeElement("CompressionTypeName", + "Compression", + DATATYPE_STRING); + + // root -> Compression -> Lossless + addElement("Lossless", "Compression", CHILD_POLICY_EMPTY); + addBooleanAttribute("Lossless", "value", true, true); + + // root -> Compression -> NumProgressiveScans + addSingleAttributeElement("NumProgressiveScans", + "Compression", + DATATYPE_INTEGER); + + // root -> Compression -> BitRate + addSingleAttributeElement("BitRate", + "Compression", + DATATYPE_FLOAT); + + // root -> Data + addElement("Data", standardMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> Data -> PlanarConfiguration + addElement("PlanarConfiguration", "Data", CHILD_POLICY_EMPTY); + + values = new ArrayList(); + values.add("PixelInterleaved"); + values.add("PlaneInterleaved"); + values.add("LineInterleaved"); + values.add("TileInterleaved"); + addAttribute("PlanarConfiguration", "value", + DATATYPE_STRING, + true, + null, + values); + + // root -> Data -> SampleFormat + addElement("SampleFormat", "Data", CHILD_POLICY_EMPTY); + + values = new ArrayList(); + values.add("SignedIntegral"); + values.add("UnsignedIntegral"); + values.add("Real"); + values.add("Index"); + addAttribute("SampleFormat", "value", + DATATYPE_STRING, + true, + null, + values); + + // root -> Data -> BitsPerSample + addElement("BitsPerSample", "Data", + CHILD_POLICY_EMPTY); + addAttribute("BitsPerSample", "value", + DATATYPE_INTEGER, + true, + 1, Integer.MAX_VALUE); + + // root -> Data -> SignificantBitsPerSample + addElement("SignificantBitsPerSample", "Data", CHILD_POLICY_EMPTY); + addAttribute("SignificantBitsPerSample", "value", + DATATYPE_INTEGER, + true, + 1, Integer.MAX_VALUE); + + // root -> Data -> SampleMSB + addElement("SampleMSB", "Data", + CHILD_POLICY_EMPTY); + addAttribute("SampleMSB", "value", + DATATYPE_INTEGER, + true, + 1, Integer.MAX_VALUE); + + // root -> Dimension + addElement("Dimension", standardMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> Dimension -> PixelAspectRatio + addSingleAttributeElement("PixelAspectRatio", + "Dimension", + DATATYPE_FLOAT); + + // root -> Dimension -> ImageOrientation + addElement("ImageOrientation", "Dimension", + CHILD_POLICY_EMPTY); + + values = new ArrayList(); + values.add("Normal"); + values.add("Rotate90"); + values.add("Rotate180"); + values.add("Rotate270"); + values.add("FlipH"); + values.add("FlipV"); + values.add("FlipHRotate90"); + values.add("FlipVRotate90"); + addAttribute("ImageOrientation", "value", + DATATYPE_STRING, + true, + null, + values); + + // root -> Dimension -> HorizontalPixelSize + addSingleAttributeElement("HorizontalPixelSize", + "Dimension", + DATATYPE_FLOAT); + + // root -> Dimension -> VerticalPixelSize + addSingleAttributeElement("VerticalPixelSize", + "Dimension", + DATATYPE_FLOAT); + + // root -> Dimension -> HorizontalPhysicalPixelSpacing + addSingleAttributeElement("HorizontalPhysicalPixelSpacing", + "Dimension", + DATATYPE_FLOAT); + + // root -> Dimension -> VerticalPhysicalPixelSpacing + addSingleAttributeElement("VerticalPhysicalPixelSpacing", + "Dimension", + DATATYPE_FLOAT); + + // root -> Dimension -> HorizontalPosition + addSingleAttributeElement("HorizontalPosition", + "Dimension", + DATATYPE_FLOAT); + + // root -> Dimension -> VerticalPosition + addSingleAttributeElement("VerticalPosition", + "Dimension", + DATATYPE_FLOAT); + + // root -> Dimension -> HorizontalPixelOffset + addSingleAttributeElement("HorizontalPixelOffset", + "Dimension", + DATATYPE_INTEGER); + + // root -> Dimension -> VerticalPixelOffset + addSingleAttributeElement("VerticalPixelOffset", + "Dimension", + DATATYPE_INTEGER); + + // root -> Dimension -> HorizontalScreenSize + addSingleAttributeElement("HorizontalScreenSize", + "Dimension", + DATATYPE_INTEGER); + + // root -> Dimension -> VerticalScreenSize + addSingleAttributeElement("VerticalScreenSize", + "Dimension", + DATATYPE_INTEGER); + + + // root -> Document + addElement("Document", standardMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> Document -> FormatVersion + addElement("FormatVersion", "Document", + CHILD_POLICY_EMPTY); + addAttribute("FormatVersion", "value", + DATATYPE_STRING, + true, + null); + + // root -> Document -> SubimageInterpretation + addElement("SubimageInterpretation", "Document", + CHILD_POLICY_EMPTY); + values = new ArrayList(); + values.add("Standalone"); + values.add("SinglePage"); + values.add("FullResolution"); + values.add("ReducedResolution"); + values.add("PyramidLayer"); + values.add("Preview"); + values.add("VolumeSlice"); + values.add("ObjectView"); + values.add("Panorama"); + values.add("AnimationFrame"); + values.add("TransparencyMask"); + values.add("CompositingLayer"); + values.add("SpectralSlice"); + values.add("Unknown"); + addAttribute("SubimageInterpretation", "value", + DATATYPE_STRING, + true, + null, + values); + + // root -> Document -> ImageCreationTime + addElement("ImageCreationTime", "Document", + CHILD_POLICY_EMPTY); + addAttribute("ImageCreationTime", "year", + DATATYPE_INTEGER, + true, + null); + addAttribute("ImageCreationTime", "month", + DATATYPE_INTEGER, + true, + null, + "1", "12", true, true); + addAttribute("ImageCreationTime", "day", + DATATYPE_INTEGER, + true, + null, + "1", "31", true, true); + addAttribute("ImageCreationTime", "hour", + DATATYPE_INTEGER, + false, + "0", + "0", "23", true, true); + addAttribute("ImageCreationTime", "minute", + DATATYPE_INTEGER, + false, + "0", + "0", "59", true, true); + // second = 60 denotes leap second + addAttribute("ImageCreationTime", "second", + DATATYPE_INTEGER, + false, + "0", + "0", "60", true, true); + + // root -> Document -> ImageModificationTime + addElement("ImageModificationTime", "Document", + CHILD_POLICY_EMPTY); + addAttribute("ImageModificationTime", "year", + DATATYPE_INTEGER, + true, + null); + addAttribute("ImageModificationTime", "month", + DATATYPE_INTEGER, + true, + null, + "1", "12", true, true); + addAttribute("ImageModificationTime", "day", + DATATYPE_INTEGER, + true, + null, + "1", "31", true, true); + addAttribute("ImageModificationTime", "hour", + DATATYPE_INTEGER, + false, + "0", + "0", "23", true, true); + addAttribute("ImageModificationTime", "minute", + DATATYPE_INTEGER, + false, + "0", + "0", "59", true, true); + // second = 60 denotes leap second + addAttribute("ImageModificationTime", "second", + DATATYPE_INTEGER, + false, + "0", + "0", "60", true, true); + + // root -> Text + addElement("Text", standardMetadataFormatName, + 0, Integer.MAX_VALUE); + + // root -> Text -> TextEntry + addElement("TextEntry", "Text", CHILD_POLICY_EMPTY); + addAttribute("TextEntry", "keyword", + DATATYPE_STRING, + false, + null); + addAttribute("TextEntry", "value", + DATATYPE_STRING, + true, + null); + addAttribute("TextEntry", "language", + DATATYPE_STRING, + false, + null); + addAttribute("TextEntry", "encoding", + DATATYPE_STRING, + false, + null); + + values = new ArrayList(); + values.add("none"); + values.add("lzw"); + values.add("zip"); + values.add("bzip"); + values.add("other"); + addAttribute("TextEntry", "compression", + DATATYPE_STRING, + false, + "none", + values); + + // root -> Transparency + addElement("Transparency", standardMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> Transparency -> Alpha + addElement("Alpha", "Transparency", CHILD_POLICY_EMPTY); + + values = new ArrayList(); + values.add("none"); + values.add("premultiplied"); + values.add("nonpremultiplied"); + addAttribute("Alpha", "value", + DATATYPE_STRING, + false, + "none", + values); + + // root -> Transparency -> TransparentIndex + addSingleAttributeElement("TransparentIndex", "Transparency", + DATATYPE_INTEGER); + + // root -> Transparency -> TransparentColor + addElement("TransparentColor", "Transparency", + CHILD_POLICY_EMPTY); + addAttribute("TransparentColor", "value", + DATATYPE_INTEGER, + true, + 0, Integer.MAX_VALUE); + + // root -> Transparency -> TileTransparencies + addElement("TileTransparencies", "Transparency", + 0, Integer.MAX_VALUE); + + // root -> Transparency -> TileTransparencies -> TransparentTile + addElement("TransparentTile", "TileTransparencies", + CHILD_POLICY_EMPTY); + addAttribute("TransparentTile", "x", + DATATYPE_INTEGER, + true, + null); + addAttribute("TransparentTile", "y", + DATATYPE_INTEGER, + true, + null); + + // root -> Transparency -> TileOpacities + addElement("TileOpacities", "Transparency", + 0, Integer.MAX_VALUE); + + // root -> Transparency -> TileOpacities -> OpaqueTile + addElement("OpaqueTile", "TileOpacities", + CHILD_POLICY_EMPTY); + addAttribute("OpaqueTile", "x", + DATATYPE_INTEGER, + true, + null); + addAttribute("OpaqueTile", "y", + DATATYPE_INTEGER, + true, + null); + } + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + return true; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/StandardMetadataFormatResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/StandardMetadataFormatResources.java new file mode 100644 index 000000000..4a94450d0 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/StandardMetadataFormatResources.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.util.ListResourceBundle; + +public class StandardMetadataFormatResources extends ListResourceBundle { + + public StandardMetadataFormatResources() {} + + protected Object[][] getContents() { + return new Object[][] { + + // Node name, followed by description, or + // Node name + "/" + AttributeName, followed by description + + { "Chroma", "Chroma (color) information" }, + + { "ColorSpaceType", "The raw color space of the image" }, + + { "NumChannels", + "The number of channels in the raw image, including alpha" }, + + { "Gamma", "The image gamma" }, + + { "BlackIsZero", + "True if smaller values represent darker shades"}, + + { "Palette", "Palette-color information" }, + + { "PaletteEntry", "A palette entry" }, + { "PaletteEntry/index", "The index of the palette entry" }, + { "PaletteEntry/red", "The red value for the palette entry" }, + { "PaletteEntry/green", "The green value for the palette entry" }, + { "PaletteEntry/blue", "The blue value for the palette entry" }, + { "PaletteEntry/alpha", "The alpha value for the palette entry" }, + + { "BackgroundIndex", "A palette index to be used as a background" }, + + { "BackgroundColor", "An RGB triple to be used as a background" }, + { "BackgroundColor/red", "The red background value" }, + { "BackgroundColor/green", "The green background value" }, + { "BackgroundColor/blue", "The blue background value" }, + + { "Compression", "Compression information" }, + + { "CompressionTypeName", "The name of the compression scheme in use" }, + + { "Lossless", + "True if the compression scheme is lossless" }, + + { "BitRate", "The estimated bit rate of the compression scheme" }, + + { "NumProgressiveScans", + "The number of progressive scans used in the image encoding"}, + + { "Data", "Information on the image layout" }, + + { "PlanarConfiguration", + "The organization of image samples in the stream" }, + + { "SampleFormat", "The numeric format of image samples" }, + + { "BitsPerSample", "The number of bits per sample"}, + { "BitsPerSample/value", + "A list of integers, one per channel" }, + + { "SignificantBitsPerSample", + "The number of significant bits per sample"}, + { "SignificantBitsPerSample/value", + "A list of integers, one per channel" }, + + { "SampleMSB", + "The position of the most significant bit of each sample"}, + { "SampleMSB/value", + "A list of integers, one per channel" }, + + { "Dimension", "Dimension information" }, + + { "PixelAspectRatio", "The width of a pixel divided by its height" }, + + { "ImageOrientation", "The desired orientation of the image in terms of flips and counter-clockwise rotations" }, + + { "HorizontalPixelSize", + "The width of a pixel, in millimeters, as it should be rendered on media" }, + + { "VerticalPixelSize", + "The height of a pixel, in millimeters, as it should be rendered on media" }, + + { "HorizontalPhysicalPixelSpacing", + "The horizontal distance in the subject of the image, in millimeters, represented by one pixel at the center of the image" }, + + { "VerticalPhysicalPixelSpacing", + "The vertical distance in the subject of the image, in millimeters, represented by one pixel at the center of the image" }, + + { "HorizontalPosition", + "The horizontal position, in millimeters, where the image should be rendered on media " }, + + { "VerticalPosition", + "The vertical position, in millimeters, where the image should be rendered on media " }, + + { "HorizontalPixelOffset", + "The horizontal position, in pixels, where the image should be rendered onto a raster display" }, + + { "VerticalPixelOffset", + "The vertical position, in pixels, where the image should be rendered onto a raster display" }, + + { "HorizontalScreenSize", + "The width, in pixels, of the raster display into which the image should be rendered" }, + + { "VerticalScreenSize", + "The height, in pixels, of the raster display into which the image should be rendered" }, + + { "Document", "Document information" }, + + { "FormatVersion", + "The version of the format used by the stream" }, + + { "SubimageInterpretation", + "The interpretation of this image in relation to the other images stored in the same stream" }, + + { "ImageCreationTime", "The time of image creation" }, + { "ImageCreationTime/year", + "The full year (e.g., 1967, not 67)" }, + { "ImageCreationTime/month", + "The month, with January = 1" }, + { "ImageCreationTime/day", + "The day of the month" }, + { "ImageCreationTime/hour", + "The hour from 0 to 23" }, + { "ImageCreationTime/minute", + "The minute from 0 to 59" }, + { "ImageCreationTime/second", + "The second from 0 to 60 (60 = leap second)" }, + + { "ImageModificationTime", "The time of the last image modification" }, + { "ImageModificationTime/year", + "The full year (e.g., 1967, not 67)" }, + { "ImageModificationTime/month", + "The month, with January = 1" }, + { "ImageModificationTime/day", + "The day of the month" }, + { "ImageModificationTime/hour", + "The hour from 0 to 23" }, + { "ImageModificationTime/minute", + "The minute from 0 to 59" }, + { "ImageModificationTime/second", + "The second from 0 to 60 (60 = leap second)" }, + + { "Text", "Text information" }, + + { "TextEntry", "A text entry"}, + { "TextEntry/keyword", "A keyword associated with the text entry" }, + { "TextEntry/value", "the text entry" }, + { "TextEntry/language", "The language of the text" }, + { "TextEntry/encoding", "The encoding of the text" }, + { "TextEntry/compression", "The method used to compress the text" }, + + { "Transparency", "Transparency information" }, + + { "Alpha", "The type of alpha information contained in the image" }, + + { "TransparentIndex", "A palette index to be treated as transparent" }, + + { "TransparentColor", "An RGB color to be treated as transparent" }, + { "TransparentColor/red", + "The red channel of the transparent color" }, + { "TransparentColor/green", + "The green channel of the transparent color" }, + { "TransparentColor/blue", + "The blue channel of the transparent color" }, + + { "TileTransparencies", "A list of completely transparent tiles" }, + + { "TransparentTile", "The index of a completely transparent tile" }, + { "TransparentTile/x", "The tile's X index" }, + { "TransparentTile/y", "The tile's Y index" }, + + { "TileOpacities", "A list of completely opaque tiles" }, + + { "OpaqueTile", "The index of a completely opaque tile" }, + { "OpaqueTile/x", "The tile's X index" }, + { "OpaqueTile/y", "The tile's Y index" }, + + }; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/SubImageInputStream.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/SubImageInputStream.java new file mode 100644 index 000000000..cdcb68dd9 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/SubImageInputStream.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2000, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.common; + +import java.io.IOException; +import javax.imageio.stream.ImageInputStreamImpl; +import javax.imageio.stream.ImageInputStream; + +public final class SubImageInputStream extends ImageInputStreamImpl { + + ImageInputStream stream; + long startingPos; + int startingLength; + int length; + + public SubImageInputStream(ImageInputStream stream, int length) + throws IOException { + this.stream = stream; + this.startingPos = stream.getStreamPosition(); + this.startingLength = this.length = length; + } + + public int read() throws IOException { + if (length == 0) { // Local EOF + return -1; + } else { + --length; + return stream.read(); + } + } + + public int read(byte[] b, int off, int len) throws IOException { + if (length == 0) { // Local EOF + return -1; + } + + len = Math.min(len, length); + int bytes = stream.read(b, off, len); + length -= bytes; + return bytes; + } + + public long length() { + return startingLength; + } + + public void seek(long pos) throws IOException { + stream.seek(pos - startingPos); + streamPos = pos; + } + + protected void finalize() throws Throwable { + // Empty finalizer (for improved performance; no need to call + // super.finalize() in this case) + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/iio-plugin.properties b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/iio-plugin.properties new file mode 100644 index 000000000..85b108f8e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/common/iio-plugin.properties @@ -0,0 +1,49 @@ +# Internationalization file for JAI plugins including +# javax.imageio.plugins.bmp +# com.sun.imageio.plugins.common +# com.sun.imageio.plugins.bmp +# com.sun.imageio.plugins.wbmp +# + +# Common properties +ImageUtil0=The supplied Raster does not represent a binary data set. +ImageUtil1=The provided sample model is null. +SimpleRenderedImage0=The provided region doesn't intersect with the image bounds. +GetNumImages0=Input has not been set. +GetNumImages1=seekForwardOnly and allowSearch cannot both be true. + + +# BMP plugin properties +BMPImageWriteParam0=Only versions 2-5 are supported. +BMPImageReader0=Only one image exists in the stream. +BMPImageReader1=Invalid magic value for BMP file. +BMPImageReader2=Invalid compression specified in BMP stream. +BMPImageReader3=New BMP version not implemented yet. +BMPImageReader4=No ImageIO-style reader is found for +BMPImageReader5=Input has not been set. +BMPImageReader6=Unable to read the image header. +BMPImageReader7=Invalid bitmap offset. +BMPImageWriter0=Output is not an ImageOutputStream. +BMPImageWriter1=The image region to be encoded is empty. +BMPImageWriter2=Only 1 or 3 band image is encoded. +BMPImageWriter3=The maximum pixel size should be 32. +BMPImageWriter4=Only version 3 is supported. +BMPImageWriter5=No ImageIO-style writer is found for +BMPImageWriter6=Compression type BI_BITFIELDS should be used for 16 bpp or 32 bpp images only. +BMPImageWriter7=Output has not been set. +BMPImageWriter8=Image is null! +BMPMetadata0=The provided metadata format isn't recognized. +BMPMetadata1=Metadata is read-only. + + +# WBMP plugin properties +WBMPImageReader0=Only one image exists in the stream. +WBMPImageReader1=Input has not been set. +WBMPImageReader2=Bad WBMP header. +WBMPImageWriter0=Output is not an ImageOutputStream. +WBMPImageWriter1=The image region to be encoded is empty. +WBMPImageWriter2=Only integral single-band bilevel image is supported. +WBMPImageWriter3=Output has not been set. +WBMPImageWriter4=Image is null! +WBMPMetadata0=The provided metadata format isn't recognized. +WBMPMetadata1=Metadata is read-only. diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadata.java new file mode 100644 index 000000000..87a45c3ca --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadata.java @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2000, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import org.w3c.dom.Node; + +public class GIFImageMetadata extends GIFMetadata { + + // package scope + static final String + nativeMetadataFormatName = "javax_imageio_gif_image_1.0"; + + static final String[] disposalMethodNames = { + "none", + "doNotDispose", + "restoreToBackgroundColor", + "restoreToPrevious", + "undefinedDisposalMethod4", + "undefinedDisposalMethod5", + "undefinedDisposalMethod6", + "undefinedDisposalMethod7" + }; + + // Fields from Image Descriptor + public int imageLeftPosition; + public int imageTopPosition; + public int imageWidth; + public int imageHeight; + public boolean interlaceFlag = false; + public boolean sortFlag = false; + public byte[] localColorTable = null; + + // Fields from Graphic Control Extension + public int disposalMethod = 0; + public boolean userInputFlag = false; + public boolean transparentColorFlag = false; + public int delayTime = 0; + public int transparentColorIndex = 0; + + // Fields from Plain Text Extension + public boolean hasPlainTextExtension = false; + public int textGridLeft; + public int textGridTop; + public int textGridWidth; + public int textGridHeight; + public int characterCellWidth; + public int characterCellHeight; + public int textForegroundColor; + public int textBackgroundColor; + public byte[] text; + + // Fields from ApplicationExtension + // List of byte[] + public List applicationIDs = null; // new ArrayList(); + + // List of byte[] + public List authenticationCodes = null; // new ArrayList(); + + // List of byte[] + public List applicationData = null; // new ArrayList(); + + // Fields from CommentExtension + // List of byte[] + public List comments = null; // new ArrayList(); + + protected GIFImageMetadata(boolean standardMetadataFormatSupported, + String nativeMetadataFormatName, + String nativeMetadataFormatClassName, + String[] extraMetadataFormatNames, + String[] extraMetadataFormatClassNames) + { + super(standardMetadataFormatSupported, + nativeMetadataFormatName, + nativeMetadataFormatClassName, + extraMetadataFormatNames, + extraMetadataFormatClassNames); + } + + public GIFImageMetadata() { + this(true, + nativeMetadataFormatName, + "com.sun.imageio.plugins.gif.GIFImageMetadataFormat", + null, null); + } + + public boolean isReadOnly() { + return true; + } + + public Node getAsTree(String formatName) { + if (formatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + private String toISO8859(byte[] data) { + try { + return new String(data, "ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + private Node getNativeTree() { + IIOMetadataNode node; // scratch node + IIOMetadataNode root = + new IIOMetadataNode(nativeMetadataFormatName); + + // Image descriptor + node = new IIOMetadataNode("ImageDescriptor"); + node.setAttribute("imageLeftPosition", + Integer.toString(imageLeftPosition)); + node.setAttribute("imageTopPosition", + Integer.toString(imageTopPosition)); + node.setAttribute("imageWidth", Integer.toString(imageWidth)); + node.setAttribute("imageHeight", Integer.toString(imageHeight)); + node.setAttribute("interlaceFlag", + interlaceFlag ? "TRUE" : "FALSE"); + root.appendChild(node); + + // Local color table + if (localColorTable != null) { + node = new IIOMetadataNode("LocalColorTable"); + int numEntries = localColorTable.length/3; + node.setAttribute("sizeOfLocalColorTable", + Integer.toString(numEntries)); + node.setAttribute("sortFlag", + sortFlag ? "TRUE" : "FALSE"); + + for (int i = 0; i < numEntries; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("ColorTableEntry"); + entry.setAttribute("index", Integer.toString(i)); + int r = localColorTable[3*i] & 0xff; + int g = localColorTable[3*i + 1] & 0xff; + int b = localColorTable[3*i + 2] & 0xff; + entry.setAttribute("red", Integer.toString(r)); + entry.setAttribute("green", Integer.toString(g)); + entry.setAttribute("blue", Integer.toString(b)); + node.appendChild(entry); + } + root.appendChild(node); + } + + // Graphic control extension + node = new IIOMetadataNode("GraphicControlExtension"); + node.setAttribute("disposalMethod", + disposalMethodNames[disposalMethod]); + node.setAttribute("userInputFlag", + userInputFlag ? "TRUE" : "FALSE"); + node.setAttribute("transparentColorFlag", + transparentColorFlag ? "TRUE" : "FALSE"); + node.setAttribute("delayTime", + Integer.toString(delayTime)); + node.setAttribute("transparentColorIndex", + Integer.toString(transparentColorIndex)); + root.appendChild(node); + + if (hasPlainTextExtension) { + node = new IIOMetadataNode("PlainTextExtension"); + node.setAttribute("textGridLeft", + Integer.toString(textGridLeft)); + node.setAttribute("textGridTop", + Integer.toString(textGridTop)); + node.setAttribute("textGridWidth", + Integer.toString(textGridWidth)); + node.setAttribute("textGridHeight", + Integer.toString(textGridHeight)); + node.setAttribute("characterCellWidth", + Integer.toString(characterCellWidth)); + node.setAttribute("characterCellHeight", + Integer.toString(characterCellHeight)); + node.setAttribute("textForegroundColor", + Integer.toString(textForegroundColor)); + node.setAttribute("textBackgroundColor", + Integer.toString(textBackgroundColor)); + node.setAttribute("text", toISO8859(text)); + + root.appendChild(node); + } + + // Application extensions + int numAppExtensions = applicationIDs == null ? + 0 : applicationIDs.size(); + if (numAppExtensions > 0) { + node = new IIOMetadataNode("ApplicationExtensions"); + for (int i = 0; i < numAppExtensions; i++) { + IIOMetadataNode appExtNode = + new IIOMetadataNode("ApplicationExtension"); + byte[] applicationID = (byte[])applicationIDs.get(i); + appExtNode.setAttribute("applicationID", + toISO8859(applicationID)); + byte[] authenticationCode = (byte[])authenticationCodes.get(i); + appExtNode.setAttribute("authenticationCode", + toISO8859(authenticationCode)); + byte[] appData = (byte[])applicationData.get(i); + appExtNode.setUserObject((byte[])appData.clone()); + node.appendChild(appExtNode); + } + + root.appendChild(node); + } + + // Comment extensions + int numComments = comments == null ? 0 : comments.size(); + if (numComments > 0) { + node = new IIOMetadataNode("CommentExtensions"); + for (int i = 0; i < numComments; i++) { + IIOMetadataNode commentNode = + new IIOMetadataNode("CommentExtension"); + byte[] comment = (byte[])comments.get(i); + commentNode.setAttribute("value", toISO8859(comment)); + node.appendChild(commentNode); + } + + root.appendChild(node); + } + + return root; + } + + public IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("ColorSpaceType"); + node.setAttribute("name", "RGB"); + chroma_node.appendChild(node); + + node = new IIOMetadataNode("NumChannels"); + node.setAttribute("value", transparentColorFlag ? "4" : "3"); + chroma_node.appendChild(node); + + // Gamma not in format + + node = new IIOMetadataNode("BlackIsZero"); + node.setAttribute("value", "TRUE"); + chroma_node.appendChild(node); + + if (localColorTable != null) { + node = new IIOMetadataNode("Palette"); + int numEntries = localColorTable.length/3; + for (int i = 0; i < numEntries; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", + Integer.toString(localColorTable[3*i] & 0xff)); + entry.setAttribute("green", + Integer.toString(localColorTable[3*i + 1] & 0xff)); + entry.setAttribute("blue", + Integer.toString(localColorTable[3*i + 2] & 0xff)); + node.appendChild(entry); + } + chroma_node.appendChild(node); + } + + // BackgroundIndex not in image + // BackgroundColor not in format + + return chroma_node; + } + + public IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("CompressionTypeName"); + node.setAttribute("value", "lzw"); + compression_node.appendChild(node); + + node = new IIOMetadataNode("Lossless"); + node.setAttribute("value", "TRUE"); + compression_node.appendChild(node); + + node = new IIOMetadataNode("NumProgressiveScans"); + node.setAttribute("value", interlaceFlag ? "4" : "1"); + compression_node.appendChild(node); + + // BitRate not in format + + return compression_node; + } + + public IIOMetadataNode getStandardDataNode() { + IIOMetadataNode data_node = new IIOMetadataNode("Data"); + IIOMetadataNode node = null; // scratch node + + // PlanarConfiguration not in format + + node = new IIOMetadataNode("SampleFormat"); + node.setAttribute("value", "Index"); + data_node.appendChild(node); + + // BitsPerSample not in image + // SignificantBitsPerSample not in format + // SampleMSB not in format + + return data_node; + } + + public IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); + IIOMetadataNode node = null; // scratch node + + // PixelAspectRatio not in image + + node = new IIOMetadataNode("ImageOrientation"); + node.setAttribute("value", "Normal"); + dimension_node.appendChild(node); + + // HorizontalPixelSize not in format + // VerticalPixelSize not in format + // HorizontalPhysicalPixelSpacing not in format + // VerticalPhysicalPixelSpacing not in format + // HorizontalPosition not in format + // VerticalPosition not in format + + node = new IIOMetadataNode("HorizontalPixelOffset"); + node.setAttribute("value", Integer.toString(imageLeftPosition)); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("VerticalPixelOffset"); + node.setAttribute("value", Integer.toString(imageTopPosition)); + dimension_node.appendChild(node); + + // HorizontalScreenSize not in image + // VerticalScreenSize not in image + + return dimension_node; + } + + // Document not in image + + public IIOMetadataNode getStandardTextNode() { + if (comments == null) { + return null; + } + Iterator commentIter = comments.iterator(); + if (!commentIter.hasNext()) { + return null; + } + + IIOMetadataNode text_node = new IIOMetadataNode("Text"); + IIOMetadataNode node = null; // scratch node + + while (commentIter.hasNext()) { + byte[] comment = (byte[])commentIter.next(); + String s = null; + try { + s = new String(comment, "ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Encoding ISO-8859-1 unknown!"); + } + + node = new IIOMetadataNode("TextEntry"); + node.setAttribute("value", s); + node.setAttribute("encoding", "ISO-8859-1"); + node.setAttribute("compression", "none"); + text_node.appendChild(node); + } + + return text_node; + } + + public IIOMetadataNode getStandardTransparencyNode() { + if (!transparentColorFlag) { + return null; + } + + IIOMetadataNode transparency_node = + new IIOMetadataNode("Transparency"); + IIOMetadataNode node = null; // scratch node + + // Alpha not in format + + node = new IIOMetadataNode("TransparentIndex"); + node.setAttribute("value", + Integer.toString(transparentColorIndex)); + transparency_node.appendChild(node); + + // TransparentColor not in format + // TileTransparencies not in format + // TileOpacities not in format + + return transparency_node; + } + + public void setFromTree(String formatName, Node root) + throws IIOInvalidTreeException + { + throw new IllegalStateException("Metadata is read-only!"); + } + + protected void mergeNativeTree(Node root) throws IIOInvalidTreeException + { + throw new IllegalStateException("Metadata is read-only!"); + } + + protected void mergeStandardTree(Node root) throws IIOInvalidTreeException + { + throw new IllegalStateException("Metadata is read-only!"); + } + + public void reset() { + throw new IllegalStateException("Metadata is read-only!"); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadataFormat.java new file mode 100644 index 000000000..fa982d7e6 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadataFormat.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2001, 2004, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.util.Arrays; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class GIFImageMetadataFormat extends IIOMetadataFormatImpl { + + private static IIOMetadataFormat instance = null; + + private GIFImageMetadataFormat() { + super(GIFImageMetadata.nativeMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> ImageDescriptor + addElement("ImageDescriptor", + GIFImageMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("ImageDescriptor", "imageLeftPosition", + DATATYPE_INTEGER, true, null, + "0", "65535", true, true); + addAttribute("ImageDescriptor", "imageTopPosition", + DATATYPE_INTEGER, true, null, + "0", "65535", true, true); + addAttribute("ImageDescriptor", "imageWidth", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("ImageDescriptor", "imageHeight", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addBooleanAttribute("ImageDescriptor", "interlaceFlag", + false, false); + + // root -> LocalColorTable + addElement("LocalColorTable", + GIFImageMetadata.nativeMetadataFormatName, + 2, 256); + addAttribute("LocalColorTable", "sizeOfLocalColorTable", + DATATYPE_INTEGER, true, null, + Arrays.asList(GIFStreamMetadata.colorTableSizes)); + addBooleanAttribute("LocalColorTable", "sortFlag", + false, false); + + // root -> LocalColorTable -> ColorTableEntry + addElement("ColorTableEntry", "LocalColorTable", + CHILD_POLICY_EMPTY); + addAttribute("ColorTableEntry", "index", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("ColorTableEntry", "red", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("ColorTableEntry", "green", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("ColorTableEntry", "blue", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + + // root -> GraphicControlExtension + addElement("GraphicControlExtension", + GIFImageMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("GraphicControlExtension", "disposalMethod", + DATATYPE_STRING, true, null, + Arrays.asList(GIFImageMetadata.disposalMethodNames)); + addBooleanAttribute("GraphicControlExtension", "userInputFlag", + false, false); + addBooleanAttribute("GraphicControlExtension", "transparentColorFlag", + false, false); + addAttribute("GraphicControlExtension", "delayTime", + DATATYPE_INTEGER, true, null, + "0", "65535", true, true); + addAttribute("GraphicControlExtension", "transparentColorIndex", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + + // root -> PlainTextExtension + addElement("PlainTextExtension", + GIFImageMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("PlainTextExtension", "textGridLeft", + DATATYPE_INTEGER, true, null, + "0", "65535", true, true); + addAttribute("PlainTextExtension", "textGridTop", + DATATYPE_INTEGER, true, null, + "0", "65535", true, true); + addAttribute("PlainTextExtension", "textGridWidth", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("PlainTextExtension", "textGridHeight", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("PlainTextExtension", "characterCellWidth", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("PlainTextExtension", "characterCellHeight", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("PlainTextExtension", "textForegroundColor", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("PlainTextExtension", "textBackgroundColor", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + + // root -> ApplicationExtensions + addElement("ApplicationExtensions", + GIFImageMetadata.nativeMetadataFormatName, + 1, Integer.MAX_VALUE); + + // root -> ApplicationExtensions -> ApplicationExtension + addElement("ApplicationExtension", "ApplicationExtensions", + CHILD_POLICY_EMPTY); + addAttribute("ApplicationExtension", "applicationID", + DATATYPE_STRING, true, null); + addAttribute("ApplicationExtension", "authenticationCode", + DATATYPE_STRING, true, null); + addObjectValue("ApplicationExtension", byte.class, + 0, Integer.MAX_VALUE); + + // root -> CommentExtensions + addElement("CommentExtensions", + GIFImageMetadata.nativeMetadataFormatName, + 1, Integer.MAX_VALUE); + + // root -> CommentExtensions -> CommentExtension + addElement("CommentExtension", "CommentExtensions", + CHILD_POLICY_EMPTY); + addAttribute("CommentExtension", "value", + DATATYPE_STRING, true, null); + } + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + return true; + } + + public static synchronized IIOMetadataFormat getInstance() { + if (instance == null) { + instance = new GIFImageMetadataFormat(); + } + return instance; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadataFormatResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadataFormatResources.java new file mode 100644 index 000000000..aa9697a8e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageMetadataFormatResources.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.util.ListResourceBundle; + +public class GIFImageMetadataFormatResources extends ListResourceBundle { + + public GIFImageMetadataFormatResources() {} + + protected Object[][] getContents() { + return new Object[][] { + + // Node name, followed by description + { "ImageDescriptor", "The image descriptor" }, + { "LocalColorTable", "The local color table" }, + { "ColorTableEntry", "A local color table entry" }, + { "GraphicControlExtension", "A graphic control extension" }, + { "PlainTextExtension", "A plain text (text grid) extension" }, + { "ApplicationExtensions", "A set of application extensions" }, + { "ApplicationExtension", "An application extension" }, + { "CommentExtensions", "A set of comments" }, + { "CommentExtension", "A comment" }, + + // Node name + "/" + AttributeName, followed by description + { "ImageDescriptor/imageLeftPosition", + "The X offset of the image relative to the screen origin" }, + { "ImageDescriptor/imageTopPosition", + "The Y offset of the image relative to the screen origin" }, + { "ImageDescriptor/imageWidth", + "The width of the image" }, + { "ImageDescriptor/imageHeight", + "The height of the image" }, + { "ImageDescriptor/interlaceFlag", + "True if the image is stored using interlacing" }, + { "LocalColorTable/sizeOfLocalColorTable", + "The number of entries in the local color table" }, + { "LocalColorTable/sortFlag", + "True if the local color table is sorted by frequency" }, + { "ColorTableEntry/index", "The index of the color table entry" }, + { "ColorTableEntry/red", + "The red value for the color table entry" }, + { "ColorTableEntry/green", + "The green value for the color table entry" }, + { "ColorTableEntry/blue", + "The blue value for the color table entry" }, + { "GraphicControlExtension/disposalMethod", + "The disposal method for this frame" }, + { "GraphicControlExtension/userInputFlag", + "True if the frame should be advanced based on user input" }, + { "GraphicControlExtension/transparentColorFlag", + "True if a transparent color exists" }, + { "GraphicControlExtension/delayTime", + "The time to delay between frames, in hundredths of a second" }, + { "GraphicControlExtension/transparentColorIndex", + "The transparent color, if transparentColorFlag is true" }, + { "PlainTextExtension/textGridLeft", + "The X offset of the text grid" }, + { "PlainTextExtension/textGridTop", + "The Y offset of the text grid" }, + { "PlainTextExtension/textGridWidth", + "The number of columns in the text grid" }, + { "PlainTextExtension/textGridHeight", + "The number of rows in the text grid" }, + { "PlainTextExtension/characterCellWidth", + "The width of a character cell" }, + { "PlainTextExtension/characterCellHeight", + "The height of a character cell" }, + { "PlainTextExtension/textForegroundColor", + "The text foreground color index" }, + { "PlainTextExtension/textBackgroundColor", + "The text background color index" }, + { "ApplicationExtension/applicationID", + "The application ID" }, + { "ApplicationExtension/authenticationCode", + "The authentication code" }, + { "CommentExtension/value", "The comment" }, + + }; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageReader.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageReader.java new file mode 100644 index 000000000..0a278e0a1 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageReader.java @@ -0,0 +1,1083 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.WritableRaster; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.imageio.IIOException; +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import com.sun.imageio.plugins.common.ReaderUtil; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.SampleModel; + +public class GIFImageReader extends ImageReader { + + // The current ImageInputStream source. + ImageInputStream stream = null; + + // Per-stream settings + + // True if the file header including stream metadata has been read. + boolean gotHeader = false; + + // Global metadata, read once per input setting. + GIFStreamMetadata streamMetadata = null; + + // The current image index + int currIndex = -1; + + // Metadata for image at 'currIndex', or null. + GIFImageMetadata imageMetadata = null; + + // A List of Longs indicating the stream positions of the + // start of the metadata for each image. Entries are added + // as needed. + List imageStartPosition = new ArrayList(); + + // Length of metadata for image at 'currIndex', valid only if + // imageMetadata != null. + int imageMetadataLength; + + // The number of images in the stream, if known, otherwise -1. + int numImages = -1; + + // Variables used by the LZW decoding process + byte[] block = new byte[255]; + int blockLength = 0; + int bitPos = 0; + int nextByte = 0; + int initCodeSize; + int clearCode; + int eofCode; + + // 32-bit lookahead buffer + int next32Bits = 0; + + // Try if the end of the data blocks has been found, + // and we are simply draining the 32-bit buffer + boolean lastBlockFound = false; + + // The image to be written. + BufferedImage theImage = null; + + // The image's tile. + WritableRaster theTile = null; + + // The image dimensions (from the stream). + int width = -1, height = -1; + + // The pixel currently being decoded (in the stream's coordinates). + int streamX = -1, streamY = -1; + + // The number of rows decoded + int rowsDone = 0; + + // The current interlace pass, starting with 0. + int interlacePass = 0; + + private byte[] fallbackColorTable = null; + + // End per-stream settings + + // Constants used to control interlacing. + static final int[] interlaceIncrement = { 8, 8, 4, 2, -1 }; + static final int[] interlaceOffset = { 0, 4, 2, 1, -1 }; + + public GIFImageReader(ImageReaderSpi originatingProvider) { + super(originatingProvider); + } + + // Take input from an ImageInputStream + public void setInput(Object input, + boolean seekForwardOnly, + boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + if (input != null) { + if (!(input instanceof ImageInputStream)) { + throw new IllegalArgumentException + ("input not an ImageInputStream!"); + } + this.stream = (ImageInputStream)input; + } else { + this.stream = null; + } + + // Clear all values based on the previous stream contents + resetStreamSettings(); + } + + public int getNumImages(boolean allowSearch) throws IIOException { + if (stream == null) { + throw new IllegalStateException("Input not set!"); + } + if (seekForwardOnly && allowSearch) { + throw new IllegalStateException + ("seekForwardOnly and allowSearch can't both be true!"); + } + + if (numImages > 0) { + return numImages; + } + if (allowSearch) { + this.numImages = locateImage(Integer.MAX_VALUE) + 1; + } + return numImages; + } + + // Throw an IndexOutOfBoundsException if index < minIndex, + // and bump minIndex if required. + private void checkIndex(int imageIndex) { + if (imageIndex < minIndex) { + throw new IndexOutOfBoundsException("imageIndex < minIndex!"); + } + if (seekForwardOnly) { + minIndex = imageIndex; + } + } + + public int getWidth(int imageIndex) throws IIOException { + checkIndex(imageIndex); + + int index = locateImage(imageIndex); + if (index != imageIndex) { + throw new IndexOutOfBoundsException(); + } + readMetadata(); + return imageMetadata.imageWidth; + } + + public int getHeight(int imageIndex) throws IIOException { + checkIndex(imageIndex); + + int index = locateImage(imageIndex); + if (index != imageIndex) { + throw new IndexOutOfBoundsException(); + } + readMetadata(); + return imageMetadata.imageHeight; + } + + // We don't check all parameters as ImageTypeSpecifier.createIndexed do + // since this method is private and we pass consistent data here + private ImageTypeSpecifier createIndexed(byte[] r, byte[] g, byte[] b, + int bits) { + ColorModel colorModel; + if (imageMetadata.transparentColorFlag) { + // Some files erroneously have a transparent color index + // of 255 even though there are fewer than 256 colors. + int idx = Math.min(imageMetadata.transparentColorIndex, + r.length - 1); + colorModel = new IndexColorModel(bits, r.length, r, g, b, idx); + } else { + colorModel = new IndexColorModel(bits, r.length, r, g, b); + } + + SampleModel sampleModel; + if (bits == 8) { + int[] bandOffsets = {0}; + sampleModel = + new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, + 1, 1, 1, 1, + bandOffsets); + } else { + sampleModel = + new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, + 1, 1, bits); + } + return new ImageTypeSpecifier(colorModel, sampleModel); + } + + public Iterator getImageTypes(int imageIndex) throws IIOException { + checkIndex(imageIndex); + + int index = locateImage(imageIndex); + if (index != imageIndex) { + throw new IndexOutOfBoundsException(); + } + readMetadata(); + + List l = new ArrayList(1); + + byte[] colorTable; + if (imageMetadata.localColorTable != null) { + colorTable = imageMetadata.localColorTable; + fallbackColorTable = imageMetadata.localColorTable; + } else { + colorTable = streamMetadata.globalColorTable; + } + + if (colorTable == null) { + if (fallbackColorTable == null) { + this.processWarningOccurred("Use default color table."); + + // no color table, the spec allows to use any palette. + fallbackColorTable = getDefaultPalette(); + } + + colorTable = fallbackColorTable; + } + + // Normalize color table length to 2^1, 2^2, 2^4, or 2^8 + int length = colorTable.length/3; + int bits; + if (length == 2) { + bits = 1; + } else if (length == 4) { + bits = 2; + } else if (length == 8 || length == 16) { + // Bump from 3 to 4 bits + bits = 4; + } else { + // Bump to 8 bits + bits = 8; + } + int lutLength = 1 << bits; + byte[] r = new byte[lutLength]; + byte[] g = new byte[lutLength]; + byte[] b = new byte[lutLength]; + + // Entries from length + 1 to lutLength - 1 will be 0 + int rgbIndex = 0; + for (int i = 0; i < length; i++) { + r[i] = colorTable[rgbIndex++]; + g[i] = colorTable[rgbIndex++]; + b[i] = colorTable[rgbIndex++]; + } + + l.add(createIndexed(r, g, b, bits)); + return l.iterator(); + } + + public ImageReadParam getDefaultReadParam() { + return new ImageReadParam(); + } + + public IIOMetadata getStreamMetadata() throws IIOException { + readHeader(); + return streamMetadata; + } + + public IIOMetadata getImageMetadata(int imageIndex) throws IIOException { + checkIndex(imageIndex); + + int index = locateImage(imageIndex); + if (index != imageIndex) { + throw new IndexOutOfBoundsException("Bad image index!"); + } + readMetadata(); + return imageMetadata; + } + + // BEGIN LZW STUFF + + private void initNext32Bits() { + next32Bits = block[0] & 0xff; + next32Bits |= (block[1] & 0xff) << 8; + next32Bits |= (block[2] & 0xff) << 16; + next32Bits |= block[3] << 24; + nextByte = 4; + } + + // Load a block (1-255 bytes) at a time, and maintain + // a 32-bit lookahead buffer that is filled from the left + // and extracted from the right. + // + // When the last block is found, we continue to + // + private int getCode(int codeSize, int codeMask) throws IOException { + if (bitPos + codeSize > 32) { + return eofCode; // No more data available + } + + int code = (next32Bits >> bitPos) & codeMask; + bitPos += codeSize; + + // Shift in a byte of new data at a time + while (bitPos >= 8 && !lastBlockFound) { + next32Bits >>>= 8; + bitPos -= 8; + + // Check if current block is out of bytes + if (nextByte >= blockLength) { + // Get next block size + blockLength = stream.readUnsignedByte(); + if (blockLength == 0) { + lastBlockFound = true; + return code; + } else { + int left = blockLength; + int off = 0; + while (left > 0) { + int nbytes = stream.read(block, off, left); + off += nbytes; + left -= nbytes; + } + nextByte = 0; + } + } + + next32Bits |= block[nextByte++] << 24; + } + + return code; + } + + public void initializeStringTable(int[] prefix, + byte[] suffix, + byte[] initial, + int[] length) { + int numEntries = 1 << initCodeSize; + for (int i = 0; i < numEntries; i++) { + prefix[i] = -1; + suffix[i] = (byte)i; + initial[i] = (byte)i; + length[i] = 1; + } + + // Fill in the entire table for robustness against + // out-of-sequence codes. + for (int i = numEntries; i < 4096; i++) { + prefix[i] = -1; + length[i] = 1; + } + + // tableIndex = numEntries + 2; + // codeSize = initCodeSize + 1; + // codeMask = (1 << codeSize) - 1; + } + + Rectangle sourceRegion; + int sourceXSubsampling; + int sourceYSubsampling; + int sourceMinProgressivePass; + int sourceMaxProgressivePass; + + Point destinationOffset; + Rectangle destinationRegion; + + // Used only if IIOReadUpdateListeners are present + int updateMinY; + int updateYStep; + + boolean decodeThisRow = true; + int destY = 0; + + byte[] rowBuf; + + private void outputRow() { + // Clip against ImageReadParam + int width = Math.min(sourceRegion.width, + destinationRegion.width*sourceXSubsampling); + int destX = destinationRegion.x; + + if (sourceXSubsampling == 1) { + theTile.setDataElements(destX, destY, width, 1, rowBuf); + } else { + for (int x = 0; x < width; x += sourceXSubsampling, destX++) { + theTile.setSample(destX, destY, 0, rowBuf[x] & 0xff); + } + } + + // Update IIOReadUpdateListeners, if any + if (updateListeners != null) { + int[] bands = { 0 }; + // updateYStep will have been initialized if + // updateListeners is non-null + processImageUpdate(theImage, + destX, destY, + width, 1, 1, updateYStep, + bands); + } + } + + private void computeDecodeThisRow() { + this.decodeThisRow = + (destY < destinationRegion.y + destinationRegion.height) && + (streamY >= sourceRegion.y) && + (streamY < sourceRegion.y + sourceRegion.height) && + (((streamY - sourceRegion.y) % sourceYSubsampling) == 0); + } + + private void outputPixels(byte[] string, int len) { + if (interlacePass < sourceMinProgressivePass || + interlacePass > sourceMaxProgressivePass) { + return; + } + + for (int i = 0; i < len; i++) { + if (streamX >= sourceRegion.x) { + rowBuf[streamX - sourceRegion.x] = string[i]; + } + + // Process end-of-row + ++streamX; + if (streamX == width) { + // Update IIOReadProgressListeners + ++rowsDone; + processImageProgress(100.0F*rowsDone/height); + + if (decodeThisRow) { + outputRow(); + } + + streamX = 0; + if (imageMetadata.interlaceFlag) { + streamY += interlaceIncrement[interlacePass]; + if (streamY >= height) { + // Inform IIOReadUpdateListeners of end of pass + if (updateListeners != null) { + processPassComplete(theImage); + } + + ++interlacePass; + if (interlacePass > sourceMaxProgressivePass) { + return; + } + streamY = interlaceOffset[interlacePass]; + startPass(interlacePass); + } + } else { + ++streamY; + } + + // Determine whether pixels from this row will + // be written to the destination + this.destY = destinationRegion.y + + (streamY - sourceRegion.y)/sourceYSubsampling; + computeDecodeThisRow(); + } + } + } + + // END LZW STUFF + + private void readHeader() throws IIOException { + if (gotHeader) { + return; + } + if (stream == null) { + throw new IllegalStateException("Input not set!"); + } + + // Create an object to store the stream metadata + this.streamMetadata = new GIFStreamMetadata(); + + try { + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + + byte[] signature = new byte[6]; + stream.readFully(signature); + + StringBuffer version = new StringBuffer(3); + version.append((char)signature[3]); + version.append((char)signature[4]); + version.append((char)signature[5]); + streamMetadata.version = version.toString(); + + streamMetadata.logicalScreenWidth = stream.readUnsignedShort(); + streamMetadata.logicalScreenHeight = stream.readUnsignedShort(); + + int packedFields = stream.readUnsignedByte(); + boolean globalColorTableFlag = (packedFields & 0x80) != 0; + streamMetadata.colorResolution = ((packedFields >> 4) & 0x7) + 1; + streamMetadata.sortFlag = (packedFields & 0x8) != 0; + int numGCTEntries = 1 << ((packedFields & 0x7) + 1); + + streamMetadata.backgroundColorIndex = stream.readUnsignedByte(); + streamMetadata.pixelAspectRatio = stream.readUnsignedByte(); + + if (globalColorTableFlag) { + streamMetadata.globalColorTable = new byte[3*numGCTEntries]; + stream.readFully(streamMetadata.globalColorTable); + } else { + streamMetadata.globalColorTable = null; + } + + // Found position of metadata for image 0 + imageStartPosition.add(Long.valueOf(stream.getStreamPosition())); + } catch (IOException e) { + throw new IIOException("I/O error reading header!", e); + } + + gotHeader = true; + } + + private boolean skipImage() throws IIOException { + // Stream must be at the beginning of an image descriptor + // upon exit + + try { + while (true) { + int blockType = stream.readUnsignedByte(); + + if (blockType == 0x2c) { + stream.skipBytes(8); + + int packedFields = stream.readUnsignedByte(); + if ((packedFields & 0x80) != 0) { + // Skip color table if any + int bits = (packedFields & 0x7) + 1; + stream.skipBytes(3*(1 << bits)); + } + + stream.skipBytes(1); + + int length = 0; + do { + length = stream.readUnsignedByte(); + stream.skipBytes(length); + } while (length > 0); + + return true; + } else if (blockType == 0x3b) { + return false; + } else if (blockType == 0x21) { + int label = stream.readUnsignedByte(); + + int length = 0; + do { + length = stream.readUnsignedByte(); + stream.skipBytes(length); + } while (length > 0); + } else if (blockType == 0x0) { + // EOF + return false; + } else { + int length = 0; + do { + length = stream.readUnsignedByte(); + stream.skipBytes(length); + } while (length > 0); + } + } + } catch (EOFException e) { + return false; + } catch (IOException e) { + throw new IIOException("I/O error locating image!", e); + } + } + + private int locateImage(int imageIndex) throws IIOException { + readHeader(); + + try { + // Find closest known index + int index = Math.min(imageIndex, imageStartPosition.size() - 1); + + // Seek to that position + Long l = (Long)imageStartPosition.get(index); + stream.seek(l.longValue()); + + // Skip images until at desired index or last image found + while (index < imageIndex) { + if (!skipImage()) { + --index; + return index; + } + + Long l1 = new Long(stream.getStreamPosition()); + imageStartPosition.add(l1); + ++index; + } + } catch (IOException e) { + throw new IIOException("Couldn't seek!", e); + } + + if (currIndex != imageIndex) { + imageMetadata = null; + } + currIndex = imageIndex; + return imageIndex; + } + + // Read blocks of 1-255 bytes, stop at a 0-length block + private byte[] concatenateBlocks() throws IOException { + byte[] data = new byte[0]; + while (true) { + int length = stream.readUnsignedByte(); + if (length == 0) { + break; + } + byte[] newData = new byte[data.length + length]; + System.arraycopy(data, 0, newData, 0, data.length); + stream.readFully(newData, data.length, length); + data = newData; + } + + return data; + } + + // Stream must be positioned at start of metadata for 'currIndex' + private void readMetadata() throws IIOException { + if (stream == null) { + throw new IllegalStateException("Input not set!"); + } + + try { + // Create an object to store the image metadata + this.imageMetadata = new GIFImageMetadata(); + + long startPosition = stream.getStreamPosition(); + while (true) { + int blockType = stream.readUnsignedByte(); + if (blockType == 0x2c) { // Image Descriptor + imageMetadata.imageLeftPosition = + stream.readUnsignedShort(); + imageMetadata.imageTopPosition = + stream.readUnsignedShort(); + imageMetadata.imageWidth = stream.readUnsignedShort(); + imageMetadata.imageHeight = stream.readUnsignedShort(); + + int idPackedFields = stream.readUnsignedByte(); + boolean localColorTableFlag = + (idPackedFields & 0x80) != 0; + imageMetadata.interlaceFlag = (idPackedFields & 0x40) != 0; + imageMetadata.sortFlag = (idPackedFields & 0x20) != 0; + int numLCTEntries = 1 << ((idPackedFields & 0x7) + 1); + + if (localColorTableFlag) { + // Read color table if any + imageMetadata.localColorTable = + new byte[3*numLCTEntries]; + stream.readFully(imageMetadata.localColorTable); + } else { + imageMetadata.localColorTable = null; + } + + // Record length of this metadata block + this.imageMetadataLength = + (int)(stream.getStreamPosition() - startPosition); + + // Now positioned at start of LZW-compressed pixels + return; + } else if (blockType == 0x21) { // Extension block + int label = stream.readUnsignedByte(); + + if (label == 0xf9) { // Graphics Control Extension + int gceLength = stream.readUnsignedByte(); // 4 + int gcePackedFields = stream.readUnsignedByte(); + imageMetadata.disposalMethod = + (gcePackedFields >> 2) & 0x3; + imageMetadata.userInputFlag = + (gcePackedFields & 0x2) != 0; + imageMetadata.transparentColorFlag = + (gcePackedFields & 0x1) != 0; + + imageMetadata.delayTime = stream.readUnsignedShort(); + imageMetadata.transparentColorIndex + = stream.readUnsignedByte(); + + int terminator = stream.readUnsignedByte(); + } else if (label == 0x1) { // Plain text extension + int length = stream.readUnsignedByte(); + imageMetadata.hasPlainTextExtension = true; + imageMetadata.textGridLeft = + stream.readUnsignedShort(); + imageMetadata.textGridTop = + stream.readUnsignedShort(); + imageMetadata.textGridWidth = + stream.readUnsignedShort(); + imageMetadata.textGridHeight = + stream.readUnsignedShort(); + imageMetadata.characterCellWidth = + stream.readUnsignedByte(); + imageMetadata.characterCellHeight = + stream.readUnsignedByte(); + imageMetadata.textForegroundColor = + stream.readUnsignedByte(); + imageMetadata.textBackgroundColor = + stream.readUnsignedByte(); + imageMetadata.text = concatenateBlocks(); + } else if (label == 0xfe) { // Comment extension + byte[] comment = concatenateBlocks(); + if (imageMetadata.comments == null) { + imageMetadata.comments = new ArrayList(); + } + imageMetadata.comments.add(comment); + } else if (label == 0xff) { // Application extension + int blockSize = stream.readUnsignedByte(); + byte[] applicationID = new byte[8]; + byte[] authCode = new byte[3]; + + // read available data + byte[] blockData = new byte[blockSize]; + stream.readFully(blockData); + + int offset = copyData(blockData, 0, applicationID); + offset = copyData(blockData, offset, authCode); + + byte[] applicationData = concatenateBlocks(); + + if (offset < blockSize) { + int len = blockSize - offset; + byte[] data = + new byte[len + applicationData.length]; + + System.arraycopy(blockData, offset, data, 0, len); + System.arraycopy(applicationData, 0, data, len, + applicationData.length); + + applicationData = data; + } + + // Init lists if necessary + if (imageMetadata.applicationIDs == null) { + imageMetadata.applicationIDs = new ArrayList(); + imageMetadata.authenticationCodes = + new ArrayList(); + imageMetadata.applicationData = new ArrayList(); + } + imageMetadata.applicationIDs.add(applicationID); + imageMetadata.authenticationCodes.add(authCode); + imageMetadata.applicationData.add(applicationData); + } else { + // Skip over unknown extension blocks + int length = 0; + do { + length = stream.readUnsignedByte(); + stream.skipBytes(length); + } while (length > 0); + } + } else if (blockType == 0x3b) { // Trailer + throw new IndexOutOfBoundsException + ("Attempt to read past end of image sequence!"); + } else { + throw new IIOException("Unexpected block type " + + blockType + "!"); + } + } + } catch (IIOException iioe) { + throw iioe; + } catch (IOException ioe) { + throw new IIOException("I/O error reading image metadata!", ioe); + } + } + + private int copyData(byte[] src, int offset, byte[] dst) { + int len = dst.length; + int rest = src.length - offset; + if (len > rest) { + len = rest; + } + System.arraycopy(src, offset, dst, 0, len); + return offset + len; + } + + private void startPass(int pass) { + if (updateListeners == null || !imageMetadata.interlaceFlag) { + return; + } + + int y = interlaceOffset[interlacePass]; + int yStep = interlaceIncrement[interlacePass]; + + int[] vals = ReaderUtil. + computeUpdatedPixels(sourceRegion, + destinationOffset, + destinationRegion.x, + destinationRegion.y, + destinationRegion.x + + destinationRegion.width - 1, + destinationRegion.y + + destinationRegion.height - 1, + sourceXSubsampling, + sourceYSubsampling, + 0, + y, + destinationRegion.width, + (destinationRegion.height + yStep - 1)/yStep, + 1, + yStep); + + // Initialized updateMinY and updateYStep + this.updateMinY = vals[1]; + this.updateYStep = vals[5]; + + // Inform IIOReadUpdateListeners of new pass + int[] bands = { 0 }; + + processPassStarted(theImage, + interlacePass, + sourceMinProgressivePass, + sourceMaxProgressivePass, + 0, + updateMinY, + 1, + updateYStep, + bands); + } + + public BufferedImage read(int imageIndex, ImageReadParam param) + throws IIOException { + if (stream == null) { + throw new IllegalStateException("Input not set!"); + } + checkIndex(imageIndex); + + int index = locateImage(imageIndex); + if (index != imageIndex) { + throw new IndexOutOfBoundsException("imageIndex out of bounds!"); + } + + clearAbortRequest(); + readMetadata(); + + // A null ImageReadParam means we use the default + if (param == null) { + param = getDefaultReadParam(); + } + + // Initialize the destination image + Iterator imageTypes = getImageTypes(imageIndex); + this.theImage = getDestination(param, + imageTypes, + imageMetadata.imageWidth, + imageMetadata.imageHeight); + this.theTile = theImage.getWritableTile(0, 0); + this.width = imageMetadata.imageWidth; + this.height = imageMetadata.imageHeight; + this.streamX = 0; + this.streamY = 0; + this.rowsDone = 0; + this.interlacePass = 0; + + // Get source region, taking subsampling offsets into account, + // and clipping against the true source bounds + + this.sourceRegion = new Rectangle(0, 0, 0, 0); + this.destinationRegion = new Rectangle(0, 0, 0, 0); + computeRegions(param, width, height, theImage, + sourceRegion, destinationRegion); + this.destinationOffset = new Point(destinationRegion.x, + destinationRegion.y); + + this.sourceXSubsampling = param.getSourceXSubsampling(); + this.sourceYSubsampling = param.getSourceYSubsampling(); + this.sourceMinProgressivePass = + Math.max(param.getSourceMinProgressivePass(), 0); + this.sourceMaxProgressivePass = + Math.min(param.getSourceMaxProgressivePass(), 3); + + this.destY = destinationRegion.y + + (streamY - sourceRegion.y)/sourceYSubsampling; + computeDecodeThisRow(); + + // Inform IIOReadProgressListeners of start of image + processImageStarted(imageIndex); + startPass(0); + + this.rowBuf = new byte[width]; + + try { + // Read and decode the image data, fill in theImage + this.initCodeSize = stream.readUnsignedByte(); + + // Read first data block + this.blockLength = stream.readUnsignedByte(); + int left = blockLength; + int off = 0; + while (left > 0) { + int nbytes = stream.read(block, off, left); + left -= nbytes; + off += nbytes; + } + + this.bitPos = 0; + this.nextByte = 0; + this.lastBlockFound = false; + this.interlacePass = 0; + + // Init 32-bit buffer + initNext32Bits(); + + this.clearCode = 1 << initCodeSize; + this.eofCode = clearCode + 1; + + int code, oldCode = 0; + + int[] prefix = new int[4096]; + byte[] suffix = new byte[4096]; + byte[] initial = new byte[4096]; + int[] length = new int[4096]; + byte[] string = new byte[4096]; + + initializeStringTable(prefix, suffix, initial, length); + int tableIndex = (1 << initCodeSize) + 2; + int codeSize = initCodeSize + 1; + int codeMask = (1 << codeSize) - 1; + + while (!abortRequested()) { + code = getCode(codeSize, codeMask); + + if (code == clearCode) { + initializeStringTable(prefix, suffix, initial, length); + tableIndex = (1 << initCodeSize) + 2; + codeSize = initCodeSize + 1; + codeMask = (1 << codeSize) - 1; + + code = getCode(codeSize, codeMask); + if (code == eofCode) { + // Inform IIOReadProgressListeners of end of image + processImageComplete(); + return theImage; + } + } else if (code == eofCode) { + // Inform IIOReadProgressListeners of end of image + processImageComplete(); + return theImage; + } else { + int newSuffixIndex; + if (code < tableIndex) { + newSuffixIndex = code; + } else { // code == tableIndex + newSuffixIndex = oldCode; + if (code != tableIndex) { + // warning - code out of sequence + // possibly data corruption + processWarningOccurred("Out-of-sequence code!"); + } + } + + int ti = tableIndex; + int oc = oldCode; + + prefix[ti] = oc; + suffix[ti] = initial[newSuffixIndex]; + initial[ti] = initial[oc]; + length[ti] = length[oc] + 1; + + ++tableIndex; + if ((tableIndex == (1 << codeSize)) && + (tableIndex < 4096)) { + ++codeSize; + codeMask = (1 << codeSize) - 1; + } + } + + // Reverse code + int c = code; + int len = length[c]; + for (int i = len - 1; i >= 0; i--) { + string[i] = suffix[c]; + c = prefix[c]; + } + + outputPixels(string, len); + oldCode = code; + } + + processReadAborted(); + return theImage; + } catch (IOException e) { + e.printStackTrace(); + throw new IIOException("I/O error reading image!", e); + } + } + + /** + * Remove all settings including global settings such as + * Locales and listeners, as well as stream settings. + */ + public void reset() { + super.reset(); + resetStreamSettings(); + } + + /** + * Remove local settings based on parsing of a stream. + */ + private void resetStreamSettings() { + gotHeader = false; + streamMetadata = null; + currIndex = -1; + imageMetadata = null; + imageStartPosition = new ArrayList(); + numImages = -1; + + // No need to reinitialize 'block' + blockLength = 0; + bitPos = 0; + nextByte = 0; + + next32Bits = 0; + lastBlockFound = false; + + theImage = null; + theTile = null; + width = -1; + height = -1; + streamX = -1; + streamY = -1; + rowsDone = 0; + interlacePass = 0; + + fallbackColorTable = null; + } + + private static byte[] defaultPalette = null; + + private static synchronized byte[] getDefaultPalette() { + if (defaultPalette == null) { + BufferedImage img = new BufferedImage(1, 1, + BufferedImage.TYPE_BYTE_INDEXED); + IndexColorModel icm = (IndexColorModel) img.getColorModel(); + + final int size = icm.getMapSize(); + byte[] r = new byte[size]; + byte[] g = new byte[size]; + byte[] b = new byte[size]; + icm.getReds(r); + icm.getGreens(g); + icm.getBlues(b); + + defaultPalette = new byte[size * 3]; + + for (int i = 0; i < size; i++) { + defaultPalette[3 * i + 0] = r[i]; + defaultPalette[3 * i + 1] = g[i]; + defaultPalette[3 * i + 2] = b[i]; + } + } + return defaultPalette; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageReaderSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageReaderSpi.java new file mode 100644 index 000000000..c611a7946 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageReaderSpi.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.io.IOException; +import java.util.Locale; +import java.util.Iterator; +import javax.imageio.ImageReader; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; + +public class GIFImageReaderSpi extends ImageReaderSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final String[] names = { "gif", "GIF" }; + + private static final String[] suffixes = { "gif" }; + + private static final String[] MIMETypes = { "image/gif" }; + + private static final String readerClassName = + "com.sun.imageio.plugins.gif.GIFImageReader"; + + private static final String[] writerSpiNames = { + "com.sun.imageio.plugins.gif.GIFImageWriterSpi" + }; + + public GIFImageReaderSpi() { + super(vendorName, + version, + names, + suffixes, + MIMETypes, + readerClassName, + new Class[] { ImageInputStream.class }, + writerSpiNames, + true, + GIFStreamMetadata.nativeMetadataFormatName, + "com.sun.imageio.plugins.gif.GIFStreamMetadataFormat", + null, null, + true, + GIFImageMetadata.nativeMetadataFormatName, + "com.sun.imageio.plugins.gif.GIFImageMetadataFormat", + null, null + ); + } + + public String getDescription(Locale locale) { + return "Standard GIF image reader"; + } + + public boolean canDecodeInput(Object input) throws IOException { + if (!(input instanceof ImageInputStream)) { + return false; + } + + ImageInputStream stream = (ImageInputStream)input; + byte[] b = new byte[6]; + stream.mark(); + stream.readFully(b); + stream.reset(); + + return b[0] == 'G' && b[1] == 'I' && b[2] == 'F' && b[3] == '8' && + (b[4] == '7' || b[4] == '9') && b[5] == 'a'; + } + + public ImageReader createReaderInstance(Object extension) { + return new GIFImageReader(this); + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageWriter.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageWriter.java new file mode 100644 index 000000000..e62db31bf --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageWriter.java @@ -0,0 +1,1317 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.image.ColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Locale; +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import com.sun.imageio.plugins.common.LZWCompressor; +import com.sun.imageio.plugins.common.PaletteBuilder; +import sun.awt.image.ByteComponentRaster; + +public class GIFImageWriter extends ImageWriter { + private static final boolean DEBUG = false; // XXX false for release! + + static final String STANDARD_METADATA_NAME = + IIOMetadataFormatImpl.standardMetadataFormatName; + + static final String STREAM_METADATA_NAME = + GIFWritableStreamMetadata.NATIVE_FORMAT_NAME; + + static final String IMAGE_METADATA_NAME = + GIFWritableImageMetadata.NATIVE_FORMAT_NAME; + + /** + * The output case to an ImageOutputStream. + */ + private ImageOutputStream stream = null; + + /** + * Whether a sequence is being written. + */ + private boolean isWritingSequence = false; + + /** + * Whether the header has been written. + */ + private boolean wroteSequenceHeader = false; + + /** + * The stream metadata of a sequence. + */ + private GIFWritableStreamMetadata theStreamMetadata = null; + + /** + * The index of the image being written. + */ + private int imageIndex = 0; + + /** + * The number of bits represented by the value which should be a + * legal length for a color table. + */ + private static int getNumBits(int value) throws IOException { + int numBits; + switch(value) { + case 2: + numBits = 1; + break; + case 4: + numBits = 2; + break; + case 8: + numBits = 3; + break; + case 16: + numBits = 4; + break; + case 32: + numBits = 5; + break; + case 64: + numBits = 6; + break; + case 128: + numBits = 7; + break; + case 256: + numBits = 8; + break; + default: + throw new IOException("Bad palette length: "+value+"!"); + } + + return numBits; + } + + /** + * Compute the source region and destination dimensions taking any + * parameter settings into account. + */ + private static void computeRegions(Rectangle sourceBounds, + Dimension destSize, + ImageWriteParam p) { + ImageWriteParam param; + int periodX = 1; + int periodY = 1; + if (p != null) { + int[] sourceBands = p.getSourceBands(); + if (sourceBands != null && + (sourceBands.length != 1 || + sourceBands[0] != 0)) { + throw new IllegalArgumentException("Cannot sub-band image!"); + } + + // Get source region and subsampling factors + Rectangle sourceRegion = p.getSourceRegion(); + if (sourceRegion != null) { + // Clip to actual image bounds + sourceRegion = sourceRegion.intersection(sourceBounds); + sourceBounds.setBounds(sourceRegion); + } + + // Adjust for subsampling offsets + int gridX = p.getSubsamplingXOffset(); + int gridY = p.getSubsamplingYOffset(); + sourceBounds.x += gridX; + sourceBounds.y += gridY; + sourceBounds.width -= gridX; + sourceBounds.height -= gridY; + + // Get subsampling factors + periodX = p.getSourceXSubsampling(); + periodY = p.getSourceYSubsampling(); + } + + // Compute output dimensions + destSize.setSize((sourceBounds.width + periodX - 1)/periodX, + (sourceBounds.height + periodY - 1)/periodY); + if (destSize.width <= 0 || destSize.height <= 0) { + throw new IllegalArgumentException("Empty source region!"); + } + } + + /** + * Create a color table from the image ColorModel and SampleModel. + */ + private static byte[] createColorTable(ColorModel colorModel, + SampleModel sampleModel) + { + byte[] colorTable; + if (colorModel instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)colorModel; + int mapSize = icm.getMapSize(); + + /** + * The GIF image format assumes that size of image palette + * is power of two. We will use closest larger power of two + * as size of color table. + */ + int ctSize = getGifPaletteSize(mapSize); + + byte[] reds = new byte[ctSize]; + byte[] greens = new byte[ctSize]; + byte[] blues = new byte[ctSize]; + icm.getReds(reds); + icm.getGreens(greens); + icm.getBlues(blues); + + /** + * fill tail of color component arrays by replica of first color + * in order to avoid appearance of extra colors in the color table + */ + for (int i = mapSize; i < ctSize; i++) { + reds[i] = reds[0]; + greens[i] = greens[0]; + blues[i] = blues[0]; + } + + colorTable = new byte[3*ctSize]; + int idx = 0; + for (int i = 0; i < ctSize; i++) { + colorTable[idx++] = reds[i]; + colorTable[idx++] = greens[i]; + colorTable[idx++] = blues[i]; + } + } else if (sampleModel.getNumBands() == 1) { + // create gray-scaled color table for single-banded images + int numBits = sampleModel.getSampleSize()[0]; + if (numBits > 8) { + numBits = 8; + } + int colorTableLength = 3*(1 << numBits); + colorTable = new byte[colorTableLength]; + for (int i = 0; i < colorTableLength; i++) { + colorTable[i] = (byte)(i/3); + } + } else { + // We do not have enough information here + // to create well-fit color table for RGB image. + colorTable = null; + } + + return colorTable; + } + + /** + * According do GIF specification size of clor table (palette here) + * must be in range from 2 to 256 and must be power of 2. + */ + private static int getGifPaletteSize(int x) { + if (x <= 2) { + return 2; + } + x = x - 1; + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >> 16); + return x + 1; + } + + + + public GIFImageWriter(GIFImageWriterSpi originatingProvider) { + super(originatingProvider); + if (DEBUG) { + System.err.println("GIF Writer is created"); + } + } + + public boolean canWriteSequence() { + return true; + } + + /** + * Merges inData into outData. The supplied + * metadata format name is attempted first and failing that the standard + * metadata format name is attempted. + */ + private void convertMetadata(String metadataFormatName, + IIOMetadata inData, + IIOMetadata outData) { + String formatName = null; + + String nativeFormatName = inData.getNativeMetadataFormatName(); + if (nativeFormatName != null && + nativeFormatName.equals(metadataFormatName)) { + formatName = metadataFormatName; + } else { + String[] extraFormatNames = inData.getExtraMetadataFormatNames(); + + if (extraFormatNames != null) { + for (int i = 0; i < extraFormatNames.length; i++) { + if (extraFormatNames[i].equals(metadataFormatName)) { + formatName = metadataFormatName; + break; + } + } + } + } + + if (formatName == null && + inData.isStandardMetadataFormatSupported()) { + formatName = STANDARD_METADATA_NAME; + } + + if (formatName != null) { + try { + Node root = inData.getAsTree(formatName); + outData.mergeTree(formatName, root); + } catch(IIOInvalidTreeException e) { + // ignore + } + } + } + + /** + * Creates a default stream metadata object and merges in the + * supplied metadata. + */ + public IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param) { + if (inData == null) { + throw new IllegalArgumentException("inData == null!"); + } + + IIOMetadata sm = getDefaultStreamMetadata(param); + + convertMetadata(STREAM_METADATA_NAME, inData, sm); + + return sm; + } + + /** + * Creates a default image metadata object and merges in the + * supplied metadata. + */ + public IIOMetadata convertImageMetadata(IIOMetadata inData, + ImageTypeSpecifier imageType, + ImageWriteParam param) { + if (inData == null) { + throw new IllegalArgumentException("inData == null!"); + } + if (imageType == null) { + throw new IllegalArgumentException("imageType == null!"); + } + + GIFWritableImageMetadata im = + (GIFWritableImageMetadata)getDefaultImageMetadata(imageType, + param); + + // Save interlace flag state. + + boolean isProgressive = im.interlaceFlag; + + convertMetadata(IMAGE_METADATA_NAME, inData, im); + + // Undo change to interlace flag if not MODE_COPY_FROM_METADATA. + + if (param != null && param.canWriteProgressive() && + param.getProgressiveMode() != param.MODE_COPY_FROM_METADATA) { + im.interlaceFlag = isProgressive; + } + + return im; + } + + public void endWriteSequence() throws IOException { + if (stream == null) { + throw new IllegalStateException("output == null!"); + } + if (!isWritingSequence) { + throw new IllegalStateException("prepareWriteSequence() was not invoked!"); + } + writeTrailer(); + resetLocal(); + } + + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param) { + GIFWritableImageMetadata imageMetadata = + new GIFWritableImageMetadata(); + + // Image dimensions + + SampleModel sampleModel = imageType.getSampleModel(); + + Rectangle sourceBounds = new Rectangle(sampleModel.getWidth(), + sampleModel.getHeight()); + Dimension destSize = new Dimension(); + computeRegions(sourceBounds, destSize, param); + + imageMetadata.imageWidth = destSize.width; + imageMetadata.imageHeight = destSize.height; + + // Interlacing + + if (param != null && param.canWriteProgressive() && + param.getProgressiveMode() == ImageWriteParam.MODE_DISABLED) { + imageMetadata.interlaceFlag = false; + } else { + imageMetadata.interlaceFlag = true; + } + + // Local color table + + ColorModel colorModel = imageType.getColorModel(); + + imageMetadata.localColorTable = + createColorTable(colorModel, sampleModel); + + // Transparency + + if (colorModel instanceof IndexColorModel) { + int transparentIndex = + ((IndexColorModel)colorModel).getTransparentPixel(); + if (transparentIndex != -1) { + imageMetadata.transparentColorFlag = true; + imageMetadata.transparentColorIndex = transparentIndex; + } + } + + return imageMetadata; + } + + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { + GIFWritableStreamMetadata streamMetadata = + new GIFWritableStreamMetadata(); + streamMetadata.version = "89a"; + return streamMetadata; + } + + public ImageWriteParam getDefaultWriteParam() { + return new GIFImageWriteParam(getLocale()); + } + + public void prepareWriteSequence(IIOMetadata streamMetadata) + throws IOException { + + if (stream == null) { + throw new IllegalStateException("Output is not set."); + } + + resetLocal(); + + // Save the possibly converted stream metadata as an instance variable. + if (streamMetadata == null) { + this.theStreamMetadata = + (GIFWritableStreamMetadata)getDefaultStreamMetadata(null); + } else { + this.theStreamMetadata = new GIFWritableStreamMetadata(); + convertMetadata(STREAM_METADATA_NAME, streamMetadata, + theStreamMetadata); + } + + this.isWritingSequence = true; + } + + public void reset() { + super.reset(); + resetLocal(); + } + + /** + * Resets locally defined instance variables. + */ + private void resetLocal() { + this.isWritingSequence = false; + this.wroteSequenceHeader = false; + this.theStreamMetadata = null; + this.imageIndex = 0; + } + + public void setOutput(Object output) { + super.setOutput(output); + if (output != null) { + if (!(output instanceof ImageOutputStream)) { + throw new + IllegalArgumentException("output is not an ImageOutputStream"); + } + this.stream = (ImageOutputStream)output; + this.stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + } else { + this.stream = null; + } + } + + public void write(IIOMetadata sm, + IIOImage iioimage, + ImageWriteParam p) throws IOException { + if (stream == null) { + throw new IllegalStateException("output == null!"); + } + if (iioimage == null) { + throw new IllegalArgumentException("iioimage == null!"); + } + if (iioimage.hasRaster()) { + throw new UnsupportedOperationException("canWriteRasters() == false!"); + } + + resetLocal(); + + GIFWritableStreamMetadata streamMetadata; + if (sm == null) { + streamMetadata = + (GIFWritableStreamMetadata)getDefaultStreamMetadata(p); + } else { + streamMetadata = + (GIFWritableStreamMetadata)convertStreamMetadata(sm, p); + } + + write(true, true, streamMetadata, iioimage, p); + } + + public void writeToSequence(IIOImage image, ImageWriteParam param) + throws IOException { + if (stream == null) { + throw new IllegalStateException("output == null!"); + } + if (image == null) { + throw new IllegalArgumentException("image == null!"); + } + if (image.hasRaster()) { + throw new UnsupportedOperationException("canWriteRasters() == false!"); + } + if (!isWritingSequence) { + throw new IllegalStateException("prepareWriteSequence() was not invoked!"); + } + + write(!wroteSequenceHeader, false, theStreamMetadata, + image, param); + + if (!wroteSequenceHeader) { + wroteSequenceHeader = true; + } + + this.imageIndex++; + } + + + private boolean needToCreateIndex(RenderedImage image) { + + SampleModel sampleModel = image.getSampleModel(); + ColorModel colorModel = image.getColorModel(); + + return sampleModel.getNumBands() != 1 || + sampleModel.getSampleSize()[0] > 8 || + colorModel.getComponentSize()[0] > 8; + } + + /** + * Writes any extension blocks, the Image Descriptor, the image data, + * and optionally the header (Signature and Logical Screen Descriptor) + * and trailer (Block Terminator). + * + * @param writeHeader Whether to write the header. + * @param writeTrailer Whether to write the trailer. + * @param sm The stream metadata or null if + * writeHeader is false. + * @param iioimage The image and image metadata. + * @param p The write parameters. + * + * @throws IllegalArgumentException if the number of bands is not 1. + * @throws IllegalArgumentException if the number of bits per sample is + * greater than 8. + * @throws IllegalArgumentException if the color component size is + * greater than 8. + * @throws IllegalArgumentException if writeHeader is + * true and sm is null. + * @throws IllegalArgumentException if writeHeader is + * false and a sequence is not being written. + */ + private void write(boolean writeHeader, + boolean writeTrailer, + IIOMetadata sm, + IIOImage iioimage, + ImageWriteParam p) throws IOException { + clearAbortRequest(); + + RenderedImage image = iioimage.getRenderedImage(); + + // Check for ability to encode image. + if (needToCreateIndex(image)) { + image = PaletteBuilder.createIndexedImage(image); + iioimage.setRenderedImage(image); + } + + ColorModel colorModel = image.getColorModel(); + SampleModel sampleModel = image.getSampleModel(); + + // Determine source region and destination dimensions. + Rectangle sourceBounds = new Rectangle(image.getMinX(), + image.getMinY(), + image.getWidth(), + image.getHeight()); + Dimension destSize = new Dimension(); + computeRegions(sourceBounds, destSize, p); + + // Convert any provided image metadata. + GIFWritableImageMetadata imageMetadata = null; + if (iioimage.getMetadata() != null) { + imageMetadata = new GIFWritableImageMetadata(); + convertMetadata(IMAGE_METADATA_NAME, iioimage.getMetadata(), + imageMetadata); + // Converted rgb image can use palette different from global. + // In order to avoid color artefacts we want to be sure we use + // appropriate palette. For this we initialize local color table + // from current color and sample models. + // At this point we can guarantee that local color table can be + // build because image was already converted to indexed or + // gray-scale representations + if (imageMetadata.localColorTable == null) { + imageMetadata.localColorTable = + createColorTable(colorModel, sampleModel); + + // in case of indexed image we should take care of + // transparent pixels + if (colorModel instanceof IndexColorModel) { + IndexColorModel icm = + (IndexColorModel)colorModel; + int index = icm.getTransparentPixel(); + imageMetadata.transparentColorFlag = (index != -1); + if (imageMetadata.transparentColorFlag) { + imageMetadata.transparentColorIndex = index; + } + /* NB: transparentColorFlag might have not beed reset for + greyscale images but explicitly reseting it here + is potentially not right thing to do until we have way + to find whether current value was explicitly set by + the user. + */ + } + } + } + + // Global color table values. + byte[] globalColorTable = null; + + // Write the header (Signature+Logical Screen Descriptor+ + // Global Color Table). + if (writeHeader) { + if (sm == null) { + throw new IllegalArgumentException("Cannot write null header!"); + } + + GIFWritableStreamMetadata streamMetadata = + (GIFWritableStreamMetadata)sm; + + // Set the version if not set. + if (streamMetadata.version == null) { + streamMetadata.version = "89a"; + } + + // Set the Logical Screen Desriptor if not set. + if (streamMetadata.logicalScreenWidth == + GIFMetadata.UNDEFINED_INTEGER_VALUE) + { + streamMetadata.logicalScreenWidth = destSize.width; + } + + if (streamMetadata.logicalScreenHeight == + GIFMetadata.UNDEFINED_INTEGER_VALUE) + { + streamMetadata.logicalScreenHeight = destSize.height; + } + + if (streamMetadata.colorResolution == + GIFMetadata.UNDEFINED_INTEGER_VALUE) + { + streamMetadata.colorResolution = colorModel != null ? + colorModel.getComponentSize()[0] : + sampleModel.getSampleSize()[0]; + } + + // Set the Global Color Table if not set, i.e., if not + // provided in the stream metadata. + if (streamMetadata.globalColorTable == null) { + if (isWritingSequence && imageMetadata != null && + imageMetadata.localColorTable != null) { + // Writing a sequence and a local color table was + // provided in the metadata of the first image: use it. + streamMetadata.globalColorTable = + imageMetadata.localColorTable; + } else if (imageMetadata == null || + imageMetadata.localColorTable == null) { + // Create a color table. + streamMetadata.globalColorTable = + createColorTable(colorModel, sampleModel); + } + } + + // Set the Global Color Table. At this point it should be + // A) the global color table provided in stream metadata, if any; + // B) the local color table of the image metadata, if any, if + // writing a sequence; + // C) a table created on the basis of the first image ColorModel + // and SampleModel if no local color table is available; or + // D) null if none of the foregoing conditions obtain (which + // should only be if a sequence is not being written and + // a local color table is provided in image metadata). + globalColorTable = streamMetadata.globalColorTable; + + // Write the header. + int bitsPerPixel; + if (globalColorTable != null) { + bitsPerPixel = getNumBits(globalColorTable.length/3); + } else if (imageMetadata != null && + imageMetadata.localColorTable != null) { + bitsPerPixel = + getNumBits(imageMetadata.localColorTable.length/3); + } else { + bitsPerPixel = sampleModel.getSampleSize(0); + } + writeHeader(streamMetadata, bitsPerPixel); + } else if (isWritingSequence) { + globalColorTable = theStreamMetadata.globalColorTable; + } else { + throw new IllegalArgumentException("Must write header for single image!"); + } + + // Write extension blocks, Image Descriptor, and image data. + writeImage(iioimage.getRenderedImage(), imageMetadata, p, + globalColorTable, sourceBounds, destSize); + + // Write the trailer. + if (writeTrailer) { + writeTrailer(); + } + } + + /** + * Writes any extension blocks, the Image Descriptor, and the image data + * + * @param iioimage The image and image metadata. + * @param param The write parameters. + * @param globalColorTable The Global Color Table. + * @param sourceBounds The source region. + * @param destSize The destination dimensions. + */ + private void writeImage(RenderedImage image, + GIFWritableImageMetadata imageMetadata, + ImageWriteParam param, byte[] globalColorTable, + Rectangle sourceBounds, Dimension destSize) + throws IOException { + ColorModel colorModel = image.getColorModel(); + SampleModel sampleModel = image.getSampleModel(); + + boolean writeGraphicsControlExtension; + if (imageMetadata == null) { + // Create default metadata. + imageMetadata = (GIFWritableImageMetadata)getDefaultImageMetadata( + new ImageTypeSpecifier(image), param); + + // Set GraphicControlExtension flag only if there is + // transparency. + writeGraphicsControlExtension = imageMetadata.transparentColorFlag; + } else { + // Check for GraphicControlExtension element. + NodeList list = null; + try { + IIOMetadataNode root = (IIOMetadataNode) + imageMetadata.getAsTree(IMAGE_METADATA_NAME); + list = root.getElementsByTagName("GraphicControlExtension"); + } catch(IllegalArgumentException iae) { + // Should never happen. + } + + // Set GraphicControlExtension flag if element present. + writeGraphicsControlExtension = + list != null && list.getLength() > 0; + + // If progressive mode is not MODE_COPY_FROM_METADATA, ensure + // the interlacing is set per the ImageWriteParam mode setting. + if (param != null && param.canWriteProgressive()) { + if (param.getProgressiveMode() == + ImageWriteParam.MODE_DISABLED) { + imageMetadata.interlaceFlag = false; + } else if (param.getProgressiveMode() == + ImageWriteParam.MODE_DEFAULT) { + imageMetadata.interlaceFlag = true; + } + } + } + + // Unset local color table if equal to global color table. + if (Arrays.equals(globalColorTable, imageMetadata.localColorTable)) { + imageMetadata.localColorTable = null; + } + + // Override dimensions + imageMetadata.imageWidth = destSize.width; + imageMetadata.imageHeight = destSize.height; + + // Write Graphics Control Extension. + if (writeGraphicsControlExtension) { + writeGraphicControlExtension(imageMetadata); + } + + // Write extension blocks. + writePlainTextExtension(imageMetadata); + writeApplicationExtension(imageMetadata); + writeCommentExtension(imageMetadata); + + // Write Image Descriptor + int bitsPerPixel = + getNumBits(imageMetadata.localColorTable == null ? + (globalColorTable == null ? + sampleModel.getSampleSize(0) : + globalColorTable.length/3) : + imageMetadata.localColorTable.length/3); + writeImageDescriptor(imageMetadata, bitsPerPixel); + + // Write image data + writeRasterData(image, sourceBounds, destSize, + param, imageMetadata.interlaceFlag); + } + + private void writeRows(RenderedImage image, LZWCompressor compressor, + int sx, int sdx, int sy, int sdy, int sw, + int dy, int ddy, int dw, int dh, + int numRowsWritten, int progressReportRowPeriod) + throws IOException { + if (DEBUG) System.out.println("Writing unoptimized"); + + int[] sbuf = new int[sw]; + byte[] dbuf = new byte[dw]; + + Raster raster = + image.getNumXTiles() == 1 && image.getNumYTiles() == 1 ? + image.getTile(0, 0) : image.getData(); + for (int y = dy; y < dh; y += ddy) { + if (numRowsWritten % progressReportRowPeriod == 0) { + if (abortRequested()) { + processWriteAborted(); + return; + } + processImageProgress((numRowsWritten*100.0F)/dh); + } + + raster.getSamples(sx, sy, sw, 1, 0, sbuf); + for (int i = 0, j = 0; i < dw; i++, j += sdx) { + dbuf[i] = (byte)sbuf[j]; + } + compressor.compress(dbuf, 0, dw); + numRowsWritten++; + sy += sdy; + } + } + + private void writeRowsOpt(byte[] data, int offset, int lineStride, + LZWCompressor compressor, + int dy, int ddy, int dw, int dh, + int numRowsWritten, int progressReportRowPeriod) + throws IOException { + if (DEBUG) System.out.println("Writing optimized"); + + offset += dy*lineStride; + lineStride *= ddy; + for (int y = dy; y < dh; y += ddy) { + if (numRowsWritten % progressReportRowPeriod == 0) { + if (abortRequested()) { + processWriteAborted(); + return; + } + processImageProgress((numRowsWritten*100.0F)/dh); + } + + compressor.compress(data, offset, dw); + numRowsWritten++; + offset += lineStride; + } + } + + private void writeRasterData(RenderedImage image, + Rectangle sourceBounds, + Dimension destSize, + ImageWriteParam param, + boolean interlaceFlag) throws IOException { + + int sourceXOffset = sourceBounds.x; + int sourceYOffset = sourceBounds.y; + int sourceWidth = sourceBounds.width; + int sourceHeight = sourceBounds.height; + + int destWidth = destSize.width; + int destHeight = destSize.height; + + int periodX; + int periodY; + if (param == null) { + periodX = 1; + periodY = 1; + } else { + periodX = param.getSourceXSubsampling(); + periodY = param.getSourceYSubsampling(); + } + + SampleModel sampleModel = image.getSampleModel(); + int bitsPerPixel = sampleModel.getSampleSize()[0]; + + int initCodeSize = bitsPerPixel; + if (initCodeSize == 1) { + initCodeSize++; + } + stream.write(initCodeSize); + + LZWCompressor compressor = + new LZWCompressor(stream, initCodeSize, false); + + /* At this moment we know that input image is indexed image. + * We can directly copy data iff: + * - no subsampling required (periodX = 1, periodY = 0) + * - we can access data directly (image is non-tiled, + * i.e. image data are in single block) + * - we can calculate offset in data buffer (next 3 lines) + */ + boolean isOptimizedCase = + periodX == 1 && periodY == 1 && + image.getNumXTiles() == 1 && image.getNumYTiles() == 1 && + sampleModel instanceof ComponentSampleModel && + image.getTile(0, 0) instanceof ByteComponentRaster && + image.getTile(0, 0).getDataBuffer() instanceof DataBufferByte; + + int numRowsWritten = 0; + + int progressReportRowPeriod = Math.max(destHeight/20, 1); + + processImageStarted(imageIndex); + + if (interlaceFlag) { + if (DEBUG) System.out.println("Writing interlaced"); + + if (isOptimizedCase) { + ByteComponentRaster tile = + (ByteComponentRaster)image.getTile(0, 0); + byte[] data = ((DataBufferByte)tile.getDataBuffer()).getData(); + ComponentSampleModel csm = + (ComponentSampleModel)tile.getSampleModel(); + int offset = csm.getOffset(sourceXOffset, sourceYOffset, 0); + // take into account the raster data offset + offset += tile.getDataOffset(0); + int lineStride = csm.getScanlineStride(); + + writeRowsOpt(data, offset, lineStride, compressor, + 0, 8, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + + if (abortRequested()) { + return; + } + + numRowsWritten += destHeight/8; + + writeRowsOpt(data, offset, lineStride, compressor, + 4, 8, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + + if (abortRequested()) { + return; + } + + numRowsWritten += (destHeight - 4)/8; + + writeRowsOpt(data, offset, lineStride, compressor, + 2, 4, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + + if (abortRequested()) { + return; + } + + numRowsWritten += (destHeight - 2)/4; + + writeRowsOpt(data, offset, lineStride, compressor, + 1, 2, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + } else { + writeRows(image, compressor, + sourceXOffset, periodX, + sourceYOffset, 8*periodY, + sourceWidth, + 0, 8, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + + if (abortRequested()) { + return; + } + + numRowsWritten += destHeight/8; + + writeRows(image, compressor, sourceXOffset, periodX, + sourceYOffset + 4*periodY, 8*periodY, + sourceWidth, + 4, 8, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + + if (abortRequested()) { + return; + } + + numRowsWritten += (destHeight - 4)/8; + + writeRows(image, compressor, sourceXOffset, periodX, + sourceYOffset + 2*periodY, 4*periodY, + sourceWidth, + 2, 4, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + + if (abortRequested()) { + return; + } + + numRowsWritten += (destHeight - 2)/4; + + writeRows(image, compressor, sourceXOffset, periodX, + sourceYOffset + periodY, 2*periodY, + sourceWidth, + 1, 2, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + } + } else { + if (DEBUG) System.out.println("Writing non-interlaced"); + + if (isOptimizedCase) { + Raster tile = image.getTile(0, 0); + byte[] data = ((DataBufferByte)tile.getDataBuffer()).getData(); + ComponentSampleModel csm = + (ComponentSampleModel)tile.getSampleModel(); + int offset = csm.getOffset(sourceXOffset, sourceYOffset, 0); + int lineStride = csm.getScanlineStride(); + + writeRowsOpt(data, offset, lineStride, compressor, + 0, 1, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + } else { + writeRows(image, compressor, + sourceXOffset, periodX, + sourceYOffset, periodY, + sourceWidth, + 0, 1, destWidth, destHeight, + numRowsWritten, progressReportRowPeriod); + } + } + + if (abortRequested()) { + return; + } + + processImageProgress(100.0F); + + compressor.flush(); + + stream.write(0x00); + + processImageComplete(); + } + + private void writeHeader(String version, + int logicalScreenWidth, + int logicalScreenHeight, + int colorResolution, + int pixelAspectRatio, + int backgroundColorIndex, + boolean sortFlag, + int bitsPerPixel, + byte[] globalColorTable) throws IOException { + try { + // Signature + stream.writeBytes("GIF"+version); + + // Screen Descriptor + // Width + stream.writeShort((short)logicalScreenWidth); + + // Height + stream.writeShort((short)logicalScreenHeight); + + // Global Color Table + // Packed fields + int packedFields = globalColorTable != null ? 0x80 : 0x00; + packedFields |= ((colorResolution - 1) & 0x7) << 4; + if (sortFlag) { + packedFields |= 0x8; + } + packedFields |= (bitsPerPixel - 1); + stream.write(packedFields); + + // Background color index + stream.write(backgroundColorIndex); + + // Pixel aspect ratio + stream.write(pixelAspectRatio); + + // Global Color Table + if (globalColorTable != null) { + stream.write(globalColorTable); + } + } catch (IOException e) { + throw new IIOException("I/O error writing header!", e); + } + } + + private void writeHeader(IIOMetadata streamMetadata, int bitsPerPixel) + throws IOException { + + GIFWritableStreamMetadata sm; + if (streamMetadata instanceof GIFWritableStreamMetadata) { + sm = (GIFWritableStreamMetadata)streamMetadata; + } else { + sm = new GIFWritableStreamMetadata(); + Node root = + streamMetadata.getAsTree(STREAM_METADATA_NAME); + sm.setFromTree(STREAM_METADATA_NAME, root); + } + + writeHeader(sm.version, + sm.logicalScreenWidth, + sm.logicalScreenHeight, + sm.colorResolution, + sm.pixelAspectRatio, + sm.backgroundColorIndex, + sm.sortFlag, + bitsPerPixel, + sm.globalColorTable); + } + + private void writeGraphicControlExtension(int disposalMethod, + boolean userInputFlag, + boolean transparentColorFlag, + int delayTime, + int transparentColorIndex) + throws IOException { + try { + stream.write(0x21); + stream.write(0xf9); + + stream.write(4); + + int packedFields = (disposalMethod & 0x3) << 2; + if (userInputFlag) { + packedFields |= 0x2; + } + if (transparentColorFlag) { + packedFields |= 0x1; + } + stream.write(packedFields); + + stream.writeShort((short)delayTime); + + stream.write(transparentColorIndex); + stream.write(0x00); + } catch (IOException e) { + throw new IIOException("I/O error writing Graphic Control Extension!", e); + } + } + + private void writeGraphicControlExtension(GIFWritableImageMetadata im) + throws IOException { + writeGraphicControlExtension(im.disposalMethod, + im.userInputFlag, + im.transparentColorFlag, + im.delayTime, + im.transparentColorIndex); + } + + private void writeBlocks(byte[] data) throws IOException { + if (data != null && data.length > 0) { + int offset = 0; + while (offset < data.length) { + int len = Math.min(data.length - offset, 255); + stream.write(len); + stream.write(data, offset, len); + offset += len; + } + } + } + + private void writePlainTextExtension(GIFWritableImageMetadata im) + throws IOException { + if (im.hasPlainTextExtension) { + try { + stream.write(0x21); + stream.write(0x1); + + stream.write(12); + + stream.writeShort(im.textGridLeft); + stream.writeShort(im.textGridTop); + stream.writeShort(im.textGridWidth); + stream.writeShort(im.textGridHeight); + stream.write(im.characterCellWidth); + stream.write(im.characterCellHeight); + stream.write(im.textForegroundColor); + stream.write(im.textBackgroundColor); + + writeBlocks(im.text); + + stream.write(0x00); + } catch (IOException e) { + throw new IIOException("I/O error writing Plain Text Extension!", e); + } + } + } + + private void writeApplicationExtension(GIFWritableImageMetadata im) + throws IOException { + if (im.applicationIDs != null) { + Iterator iterIDs = im.applicationIDs.iterator(); + Iterator iterCodes = im.authenticationCodes.iterator(); + Iterator iterData = im.applicationData.iterator(); + + while (iterIDs.hasNext()) { + try { + stream.write(0x21); + stream.write(0xff); + + stream.write(11); + stream.write((byte[])iterIDs.next(), 0, 8); + stream.write((byte[])iterCodes.next(), 0, 3); + + writeBlocks((byte[])iterData.next()); + + stream.write(0x00); + } catch (IOException e) { + throw new IIOException("I/O error writing Application Extension!", e); + } + } + } + } + + private void writeCommentExtension(GIFWritableImageMetadata im) + throws IOException { + if (im.comments != null) { + try { + Iterator iter = im.comments.iterator(); + while (iter.hasNext()) { + stream.write(0x21); + stream.write(0xfe); + writeBlocks((byte[])iter.next()); + stream.write(0x00); + } + } catch (IOException e) { + throw new IIOException("I/O error writing Comment Extension!", e); + } + } + } + + private void writeImageDescriptor(int imageLeftPosition, + int imageTopPosition, + int imageWidth, + int imageHeight, + boolean interlaceFlag, + boolean sortFlag, + int bitsPerPixel, + byte[] localColorTable) + throws IOException { + + try { + stream.write(0x2c); + + stream.writeShort((short)imageLeftPosition); + stream.writeShort((short)imageTopPosition); + stream.writeShort((short)imageWidth); + stream.writeShort((short)imageHeight); + + int packedFields = localColorTable != null ? 0x80 : 0x00; + if (interlaceFlag) { + packedFields |= 0x40; + } + if (sortFlag) { + packedFields |= 0x8; + } + packedFields |= (bitsPerPixel - 1); + stream.write(packedFields); + + if (localColorTable != null) { + stream.write(localColorTable); + } + } catch (IOException e) { + throw new IIOException("I/O error writing Image Descriptor!", e); + } + } + + private void writeImageDescriptor(GIFWritableImageMetadata imageMetadata, + int bitsPerPixel) + throws IOException { + + writeImageDescriptor(imageMetadata.imageLeftPosition, + imageMetadata.imageTopPosition, + imageMetadata.imageWidth, + imageMetadata.imageHeight, + imageMetadata.interlaceFlag, + imageMetadata.sortFlag, + bitsPerPixel, + imageMetadata.localColorTable); + } + + private void writeTrailer() throws IOException { + stream.write(0x3b); + } +} + +class GIFImageWriteParam extends ImageWriteParam { + GIFImageWriteParam(Locale locale) { + super(locale); + this.canWriteCompressed = true; + this.canWriteProgressive = true; + this.compressionTypes = new String[] {"LZW", "lzw"}; + this.compressionType = compressionTypes[0]; + } + + public void setCompressionMode(int mode) { + if (mode == MODE_DISABLED) { + throw new UnsupportedOperationException("MODE_DISABLED is not supported."); + } + super.setCompressionMode(mode); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageWriterSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageWriterSpi.java new file mode 100644 index 000000000..4f27ed91f --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFImageWriterSpi.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.awt.image.ColorModel; +import java.awt.image.SampleModel; +import java.util.Locale; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import com.sun.imageio.plugins.common.PaletteBuilder; + +public class GIFImageWriterSpi extends ImageWriterSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final String[] names = { "gif", "GIF" }; + + private static final String[] suffixes = { "gif" }; + + private static final String[] MIMETypes = { "image/gif" }; + + private static final String writerClassName = + "com.sun.imageio.plugins.gif.GIFImageWriter"; + + private static final String[] readerSpiNames = { + "com.sun.imageio.plugins.gif.GIFImageReaderSpi" + }; + + public GIFImageWriterSpi() { + super(vendorName, + version, + names, + suffixes, + MIMETypes, + writerClassName, + new Class[] { ImageOutputStream.class }, + readerSpiNames, + true, + GIFWritableStreamMetadata.NATIVE_FORMAT_NAME, + "com.sun.imageio.plugins.gif.GIFStreamMetadataFormat", + null, null, + true, + GIFWritableImageMetadata.NATIVE_FORMAT_NAME, + "com.sun.imageio.plugins.gif.GIFImageMetadataFormat", + null, null + ); + } + + public boolean canEncodeImage(ImageTypeSpecifier type) { + if (type == null) { + throw new IllegalArgumentException("type == null!"); + } + + SampleModel sm = type.getSampleModel(); + ColorModel cm = type.getColorModel(); + + boolean canEncode = sm.getNumBands() == 1 && + sm.getSampleSize(0) <= 8 && + sm.getWidth() <= 65535 && + sm.getHeight() <= 65535 && + (cm == null || cm.getComponentSize()[0] <= 8); + + if (canEncode) { + return true; + } else { + return PaletteBuilder.canCreatePalette(type); + } + } + + public String getDescription(Locale locale) { + return "Standard GIF image writer"; + } + + public ImageWriter createWriterInstance(Object extension) { + return new GIFImageWriter(this); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFMetadata.java new file mode 100644 index 000000000..7e03f0fb9 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFMetadata.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import org.w3c.dom.Node; + +/** + * Class which adds utility DOM element attribute access methods to + * IIOMetadata for subclass use. + */ +abstract class GIFMetadata extends IIOMetadata { + + /** + * Represents an undefined value of integer attributes. + */ + static final int UNDEFINED_INTEGER_VALUE = -1; + + // + // Note: These attribute methods were shamelessly lifted from + // com.sun.imageio.plugins.png.PNGMetadata and modified. + // + + // Shorthand for throwing an IIOInvalidTreeException + protected static void fatal(Node node, String reason) + throws IIOInvalidTreeException { + throw new IIOInvalidTreeException(reason, node); + } + + // Get an integer-valued attribute + protected static String getStringAttribute(Node node, String name, + String defaultValue, + boolean required, + String[] range) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + String value = attr.getNodeValue(); + + if (range != null) { + if (value == null) { + fatal(node, + "Null value for "+node.getNodeName()+ + " attribute "+name+"!"); + } + boolean validValue = false; + int len = range.length; + for (int i = 0; i < len; i++) { + if (value.equals(range[i])) { + validValue = true; + break; + } + } + if (!validValue) { + fatal(node, + "Bad value for "+node.getNodeName()+ + " attribute "+name+"!"); + } + } + + return value; + } + + + // Get an integer-valued attribute + protected static int getIntAttribute(Node node, String name, + int defaultValue, boolean required, + boolean bounded, int min, int max) + throws IIOInvalidTreeException { + String value = getStringAttribute(node, name, null, required, null); + if (value == null || "".equals(value)) { + return defaultValue; + } + + int intValue = defaultValue; + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + fatal(node, + "Bad value for "+node.getNodeName()+ + " attribute "+name+"!"); + } + if (bounded && (intValue < min || intValue > max)) { + fatal(node, + "Bad value for "+node.getNodeName()+ + " attribute "+name+"!"); + } + return intValue; + } + + // Get a float-valued attribute + protected static float getFloatAttribute(Node node, String name, + float defaultValue, + boolean required) + throws IIOInvalidTreeException { + String value = getStringAttribute(node, name, null, required, null); + if (value == null) { + return defaultValue; + } + return Float.parseFloat(value); + } + + // Get a required integer-valued attribute + protected static int getIntAttribute(Node node, String name, + boolean bounded, int min, int max) + throws IIOInvalidTreeException { + return getIntAttribute(node, name, -1, true, bounded, min, max); + } + + // Get a required float-valued attribute + protected static float getFloatAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getFloatAttribute(node, name, -1.0F, true); + } + + // Get a boolean-valued attribute + protected static boolean getBooleanAttribute(Node node, String name, + boolean defaultValue, + boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + String value = attr.getNodeValue(); + // Allow lower case booleans for backward compatibility, #5082756 + if (value.equals("TRUE") || value.equals("true")) { + return true; + } else if (value.equals("FALSE") || value.equals("false")) { + return false; + } else { + fatal(node, "Attribute " + name + " must be 'TRUE' or 'FALSE'!"); + return false; + } + } + + // Get a required boolean-valued attribute + protected static boolean getBooleanAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getBooleanAttribute(node, name, false, true); + } + + // Get an enumerated attribute as an index into a String array + protected static int getEnumeratedAttribute(Node node, + String name, + String[] legalNames, + int defaultValue, + boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + String value = attr.getNodeValue(); + for (int i = 0; i < legalNames.length; i++) { + if(value.equals(legalNames[i])) { + return i; + } + } + + fatal(node, "Illegal value for attribute " + name + "!"); + return -1; + } + + // Get a required enumerated attribute as an index into a String array + protected static int getEnumeratedAttribute(Node node, + String name, + String[] legalNames) + throws IIOInvalidTreeException { + return getEnumeratedAttribute(node, name, legalNames, -1, true); + } + + // Get a String-valued attribute + protected static String getAttribute(Node node, String name, + String defaultValue, boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + return attr.getNodeValue(); + } + + // Get a required String-valued attribute + protected static String getAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getAttribute(node, name, null, true); + } + + protected GIFMetadata(boolean standardMetadataFormatSupported, + String nativeMetadataFormatName, + String nativeMetadataFormatClassName, + String[] extraMetadataFormatNames, + String[] extraMetadataFormatClassNames) { + super(standardMetadataFormatSupported, + nativeMetadataFormatName, + nativeMetadataFormatClassName, + extraMetadataFormatNames, + extraMetadataFormatClassNames); + } + + public void mergeTree(String formatName, Node root) + throws IIOInvalidTreeException { + if (formatName.equals(nativeMetadataFormatName)) { + if (root == null) { + throw new IllegalArgumentException("root == null!"); + } + mergeNativeTree(root); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + if (root == null) { + throw new IllegalArgumentException("root == null!"); + } + mergeStandardTree(root); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + protected byte[] getColorTable(Node colorTableNode, + String entryNodeName, + boolean lengthExpected, + int expectedLength) + throws IIOInvalidTreeException { + byte[] red = new byte[256]; + byte[] green = new byte[256]; + byte[] blue = new byte[256]; + int maxIndex = -1; + + Node entry = colorTableNode.getFirstChild(); + if (entry == null) { + fatal(colorTableNode, "Palette has no entries!"); + } + + while (entry != null) { + if (!entry.getNodeName().equals(entryNodeName)) { + fatal(colorTableNode, + "Only a "+entryNodeName+" may be a child of a "+ + entry.getNodeName()+"!"); + } + + int index = getIntAttribute(entry, "index", true, 0, 255); + if (index > maxIndex) { + maxIndex = index; + } + red[index] = (byte)getIntAttribute(entry, "red", true, 0, 255); + green[index] = (byte)getIntAttribute(entry, "green", true, 0, 255); + blue[index] = (byte)getIntAttribute(entry, "blue", true, 0, 255); + + entry = entry.getNextSibling(); + } + + int numEntries = maxIndex + 1; + + if (lengthExpected && numEntries != expectedLength) { + fatal(colorTableNode, "Unexpected length for palette!"); + } + + byte[] colorTable = new byte[3*numEntries]; + for (int i = 0, j = 0; i < numEntries; i++) { + colorTable[j++] = red[i]; + colorTable[j++] = green[i]; + colorTable[j++] = blue[i]; + } + + return colorTable; + } + + protected abstract void mergeNativeTree(Node root) + throws IIOInvalidTreeException; + + protected abstract void mergeStandardTree(Node root) + throws IIOInvalidTreeException; +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadata.java new file mode 100644 index 000000000..31c1305b5 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadata.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import org.w3c.dom.Node; + +// TODO - document elimination of globalColorTableFlag + +public class GIFStreamMetadata extends GIFMetadata { + + // package scope + static final String + nativeMetadataFormatName = "javax_imageio_gif_stream_1.0"; + + static final String[] versionStrings = { "87a", "89a" }; + + public String version; // 87a or 89a + public int logicalScreenWidth; + public int logicalScreenHeight; + public int colorResolution; // 1 to 8 + public int pixelAspectRatio; + + public int backgroundColorIndex; // Valid if globalColorTable != null + public boolean sortFlag; // Valid if globalColorTable != null + + static final String[] colorTableSizes = { + "2", "4", "8", "16", "32", "64", "128", "256" + }; + + // Set global color table flag in header to 0 if null, 1 otherwise + public byte[] globalColorTable = null; + + protected GIFStreamMetadata(boolean standardMetadataFormatSupported, + String nativeMetadataFormatName, + String nativeMetadataFormatClassName, + String[] extraMetadataFormatNames, + String[] extraMetadataFormatClassNames) + { + super(standardMetadataFormatSupported, + nativeMetadataFormatName, + nativeMetadataFormatClassName, + extraMetadataFormatNames, + extraMetadataFormatClassNames); + } + + public GIFStreamMetadata() { + this(true, + nativeMetadataFormatName, + "com.sun.imageio.plugins.gif.GIFStreamMetadataFormat", + null, null); + + } + + public boolean isReadOnly() { + return true; + } + + public Node getAsTree(String formatName) { + if (formatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + private Node getNativeTree() { + IIOMetadataNode node; // scratch node + IIOMetadataNode root = + new IIOMetadataNode(nativeMetadataFormatName); + + node = new IIOMetadataNode("Version"); + node.setAttribute("value", version); + root.appendChild(node); + + // Image descriptor + node = new IIOMetadataNode("LogicalScreenDescriptor"); + /* NB: At the moment we use empty strings to support undefined + * integer values in tree representation. + * We need to add better support for undefined/default values later. + */ + node.setAttribute("logicalScreenWidth", + logicalScreenWidth == UNDEFINED_INTEGER_VALUE ? + "" : Integer.toString(logicalScreenWidth)); + node.setAttribute("logicalScreenHeight", + logicalScreenHeight == UNDEFINED_INTEGER_VALUE ? + "" : Integer.toString(logicalScreenHeight)); + // Stored value plus one + node.setAttribute("colorResolution", + colorResolution == UNDEFINED_INTEGER_VALUE ? + "" : Integer.toString(colorResolution)); + node.setAttribute("pixelAspectRatio", + Integer.toString(pixelAspectRatio)); + root.appendChild(node); + + if (globalColorTable != null) { + node = new IIOMetadataNode("GlobalColorTable"); + int numEntries = globalColorTable.length/3; + node.setAttribute("sizeOfGlobalColorTable", + Integer.toString(numEntries)); + node.setAttribute("backgroundColorIndex", + Integer.toString(backgroundColorIndex)); + node.setAttribute("sortFlag", + sortFlag ? "TRUE" : "FALSE"); + + for (int i = 0; i < numEntries; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("ColorTableEntry"); + entry.setAttribute("index", Integer.toString(i)); + int r = globalColorTable[3*i] & 0xff; + int g = globalColorTable[3*i + 1] & 0xff; + int b = globalColorTable[3*i + 2] & 0xff; + entry.setAttribute("red", Integer.toString(r)); + entry.setAttribute("green", Integer.toString(g)); + entry.setAttribute("blue", Integer.toString(b)); + node.appendChild(entry); + } + root.appendChild(node); + } + + return root; + } + + public IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("ColorSpaceType"); + node.setAttribute("name", "RGB"); + chroma_node.appendChild(node); + + node = new IIOMetadataNode("BlackIsZero"); + node.setAttribute("value", "TRUE"); + chroma_node.appendChild(node); + + // NumChannels not in stream + // Gamma not in format + + if (globalColorTable != null) { + node = new IIOMetadataNode("Palette"); + int numEntries = globalColorTable.length/3; + for (int i = 0; i < numEntries; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", + Integer.toString(globalColorTable[3*i] & 0xff)); + entry.setAttribute("green", + Integer.toString(globalColorTable[3*i + 1] & 0xff)); + entry.setAttribute("blue", + Integer.toString(globalColorTable[3*i + 2] & 0xff)); + node.appendChild(entry); + } + chroma_node.appendChild(node); + + // backgroundColorIndex is valid iff there is a color table + node = new IIOMetadataNode("BackgroundIndex"); + node.setAttribute("value", Integer.toString(backgroundColorIndex)); + chroma_node.appendChild(node); + } + + return chroma_node; + } + + public IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("CompressionTypeName"); + node.setAttribute("value", "lzw"); + compression_node.appendChild(node); + + node = new IIOMetadataNode("Lossless"); + node.setAttribute("value", "TRUE"); + compression_node.appendChild(node); + + // NumProgressiveScans not in stream + // BitRate not in format + + return compression_node; + } + + public IIOMetadataNode getStandardDataNode() { + IIOMetadataNode data_node = new IIOMetadataNode("Data"); + IIOMetadataNode node = null; // scratch node + + // PlanarConfiguration + + node = new IIOMetadataNode("SampleFormat"); + node.setAttribute("value", "Index"); + data_node.appendChild(node); + + node = new IIOMetadataNode("BitsPerSample"); + node.setAttribute("value", + colorResolution == UNDEFINED_INTEGER_VALUE ? + "" : Integer.toString(colorResolution)); + data_node.appendChild(node); + + // SignificantBitsPerSample + // SampleMSB + + return data_node; + } + + public IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("PixelAspectRatio"); + float aspectRatio = 1.0F; + if (pixelAspectRatio != 0) { + aspectRatio = (pixelAspectRatio + 15)/64.0F; + } + node.setAttribute("value", Float.toString(aspectRatio)); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("ImageOrientation"); + node.setAttribute("value", "Normal"); + dimension_node.appendChild(node); + + // HorizontalPixelSize not in format + // VerticalPixelSize not in format + // HorizontalPhysicalPixelSpacing not in format + // VerticalPhysicalPixelSpacing not in format + // HorizontalPosition not in format + // VerticalPosition not in format + // HorizontalPixelOffset not in stream + // VerticalPixelOffset not in stream + + node = new IIOMetadataNode("HorizontalScreenSize"); + node.setAttribute("value", + logicalScreenWidth == UNDEFINED_INTEGER_VALUE ? + "" : Integer.toString(logicalScreenWidth)); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("VerticalScreenSize"); + node.setAttribute("value", + logicalScreenHeight == UNDEFINED_INTEGER_VALUE ? + "" : Integer.toString(logicalScreenHeight)); + dimension_node.appendChild(node); + + return dimension_node; + } + + public IIOMetadataNode getStandardDocumentNode() { + IIOMetadataNode document_node = new IIOMetadataNode("Document"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("FormatVersion"); + node.setAttribute("value", version); + document_node.appendChild(node); + + // SubimageInterpretation not in format + // ImageCreationTime not in format + // ImageModificationTime not in format + + return document_node; + } + + public IIOMetadataNode getStandardTextNode() { + // Not in stream + return null; + } + + public IIOMetadataNode getStandardTransparencyNode() { + // Not in stream + return null; + } + + public void setFromTree(String formatName, Node root) + throws IIOInvalidTreeException + { + throw new IllegalStateException("Metadata is read-only!"); + } + + protected void mergeNativeTree(Node root) throws IIOInvalidTreeException + { + throw new IllegalStateException("Metadata is read-only!"); + } + + protected void mergeStandardTree(Node root) throws IIOInvalidTreeException + { + throw new IllegalStateException("Metadata is read-only!"); + } + + public void reset() { + throw new IllegalStateException("Metadata is read-only!"); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadataFormat.java new file mode 100644 index 000000000..b598674cc --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadataFormat.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2001, 2004, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.util.Arrays; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class GIFStreamMetadataFormat extends IIOMetadataFormatImpl { + + private static IIOMetadataFormat instance = null; + + private GIFStreamMetadataFormat() { + super(GIFStreamMetadata.nativeMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> Version + addElement("Version", GIFStreamMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("Version", "value", + DATATYPE_STRING, true, null, + Arrays.asList(GIFStreamMetadata.versionStrings)); + + // root -> LogicalScreenDescriptor + addElement("LogicalScreenDescriptor", + GIFStreamMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + addAttribute("LogicalScreenDescriptor", "logicalScreenWidth", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("LogicalScreenDescriptor", "logicalScreenHeight", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + addAttribute("LogicalScreenDescriptor", "colorResolution", + DATATYPE_INTEGER, true, null, + "1", "8", true, true); + addAttribute("LogicalScreenDescriptor", "pixelAspectRatio", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + + // root -> GlobalColorTable + addElement("GlobalColorTable", + GIFStreamMetadata.nativeMetadataFormatName, + 2, 256); + addAttribute("GlobalColorTable", "sizeOfGlobalColorTable", + DATATYPE_INTEGER, true, null, + Arrays.asList(GIFStreamMetadata.colorTableSizes)); + addAttribute("GlobalColorTable", "backgroundColorIndex", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addBooleanAttribute("GlobalColorTable", "sortFlag", + false, false); + + // root -> GlobalColorTable -> ColorTableEntry + addElement("ColorTableEntry", "GlobalColorTable", + CHILD_POLICY_EMPTY); + addAttribute("ColorTableEntry", "index", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("ColorTableEntry", "red", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("ColorTableEntry", "green", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + addAttribute("ColorTableEntry", "blue", + DATATYPE_INTEGER, true, null, + "0", "255", true, true); + } + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + return true; + } + + public static synchronized IIOMetadataFormat getInstance() { + if (instance == null) { + instance = new GIFStreamMetadataFormat(); + } + return instance; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadataFormatResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadataFormatResources.java new file mode 100644 index 000000000..61aa81592 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFStreamMetadataFormatResources.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.util.ListResourceBundle; + +public class GIFStreamMetadataFormatResources extends ListResourceBundle { + + public GIFStreamMetadataFormatResources() {} + + protected Object[][] getContents() { + return new Object[][] { + + // Node name, followed by description + { "Version", "The file version, either 87a or 89a" }, + { "LogicalScreenDescriptor", + "The logical screen descriptor, except for the global color table" }, + { "GlobalColorTable", "The global color table" }, + { "ColorTableEntry", "A global color table entry" }, + + // Node name + "/" + AttributeName, followed by description + { "Version/value", + "The version string" }, + { "LogicalScreenDescriptor/logicalScreenWidth", + "The width in pixels of the whole picture" }, + { "LogicalScreenDescriptor/logicalScreenHeight", + "The height in pixels of the whole picture" }, + { "LogicalScreenDescriptor/colorResolution", + "The number of bits of color resolution, beteen 1 and 8" }, + { "LogicalScreenDescriptor/pixelAspectRatio", + "If 0, indicates square pixels, else W/H = (value + 15)/64" }, + { "GlobalColorTable/sizeOfGlobalColorTable", + "The number of entries in the global color table" }, + { "GlobalColorTable/backgroundColorIndex", + "The index of the color table entry to be used as a background" }, + { "GlobalColorTable/sortFlag", + "True if the global color table is sorted by frequency" }, + { "ColorTableEntry/index", "The index of the color table entry" }, + { "ColorTableEntry/red", + "The red value for the color table entry" }, + { "ColorTableEntry/green", + "The green value for the color table entry" }, + { "ColorTableEntry/blue", + "The blue value for the color table entry" }, + + }; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFWritableImageMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFWritableImageMetadata.java new file mode 100644 index 000000000..3a8755430 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFWritableImageMetadata.java @@ -0,0 +1,402 @@ +/* + * Copyright (c) 2005, 2008, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import org.w3c.dom.Node; + +class GIFWritableImageMetadata extends GIFImageMetadata { + + // package scope + static final String + NATIVE_FORMAT_NAME = "javax_imageio_gif_image_1.0"; + + GIFWritableImageMetadata() { + super(true, + NATIVE_FORMAT_NAME, + "com.sun.imageio.plugins.gif.GIFImageMetadataFormat", + null, null); + } + + public boolean isReadOnly() { + return false; + } + + public void reset() { + // Fields from Image Descriptor + imageLeftPosition = 0; + imageTopPosition = 0; + imageWidth = 0; + imageHeight = 0; + interlaceFlag = false; + sortFlag = false; + localColorTable = null; + + // Fields from Graphic Control Extension + disposalMethod = 0; + userInputFlag = false; + transparentColorFlag = false; + delayTime = 0; + transparentColorIndex = 0; + + // Fields from Plain Text Extension + hasPlainTextExtension = false; + textGridLeft = 0; + textGridTop = 0; + textGridWidth = 0; + textGridHeight = 0; + characterCellWidth = 0; + characterCellHeight = 0; + textForegroundColor = 0; + textBackgroundColor = 0; + text = null; + + // Fields from ApplicationExtension + applicationIDs = null; + authenticationCodes = null; + applicationData = null; + + // Fields from CommentExtension + // List of byte[] + comments = null; + } + + private byte[] fromISO8859(String data) { + try { + return data.getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + return "".getBytes(); + } + } + + protected void mergeNativeTree(Node root) throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName().equals(nativeMetadataFormatName)) { + fatal(node, "Root must be " + nativeMetadataFormatName); + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("ImageDescriptor")) { + imageLeftPosition = getIntAttribute(node, + "imageLeftPosition", + -1, true, + true, 0, 65535); + + imageTopPosition = getIntAttribute(node, + "imageTopPosition", + -1, true, + true, 0, 65535); + + imageWidth = getIntAttribute(node, + "imageWidth", + -1, true, + true, 1, 65535); + + imageHeight = getIntAttribute(node, + "imageHeight", + -1, true, + true, 1, 65535); + + interlaceFlag = getBooleanAttribute(node, "interlaceFlag", + false, true); + } else if (name.equals("LocalColorTable")) { + int sizeOfLocalColorTable = + getIntAttribute(node, "sizeOfLocalColorTable", + true, 2, 256); + if (sizeOfLocalColorTable != 2 && + sizeOfLocalColorTable != 4 && + sizeOfLocalColorTable != 8 && + sizeOfLocalColorTable != 16 && + sizeOfLocalColorTable != 32 && + sizeOfLocalColorTable != 64 && + sizeOfLocalColorTable != 128 && + sizeOfLocalColorTable != 256) { + fatal(node, + "Bad value for LocalColorTable attribute sizeOfLocalColorTable!"); + } + + sortFlag = getBooleanAttribute(node, "sortFlag", false, true); + + localColorTable = getColorTable(node, "ColorTableEntry", + true, sizeOfLocalColorTable); + } else if (name.equals("GraphicControlExtension")) { + String disposalMethodName = + getStringAttribute(node, "disposalMethod", null, + true, disposalMethodNames); + disposalMethod = 0; + while(!disposalMethodName.equals(disposalMethodNames[disposalMethod])) { + disposalMethod++; + } + + userInputFlag = getBooleanAttribute(node, "userInputFlag", + false, true); + + transparentColorFlag = + getBooleanAttribute(node, "transparentColorFlag", + false, true); + + delayTime = getIntAttribute(node, + "delayTime", + -1, true, + true, 0, 65535); + + transparentColorIndex = + getIntAttribute(node, "transparentColorIndex", + -1, true, + true, 0, 65535); + } else if (name.equals("PlainTextExtension")) { + hasPlainTextExtension = true; + + textGridLeft = getIntAttribute(node, + "textGridLeft", + -1, true, + true, 0, 65535); + + textGridTop = getIntAttribute(node, + "textGridTop", + -1, true, + true, 0, 65535); + + textGridWidth = getIntAttribute(node, + "textGridWidth", + -1, true, + true, 1, 65535); + + textGridHeight = getIntAttribute(node, + "textGridHeight", + -1, true, + true, 1, 65535); + + characterCellWidth = getIntAttribute(node, + "characterCellWidth", + -1, true, + true, 1, 65535); + + characterCellHeight = getIntAttribute(node, + "characterCellHeight", + -1, true, + true, 1, 65535); + + textForegroundColor = getIntAttribute(node, + "textForegroundColor", + -1, true, + true, 0, 255); + + textBackgroundColor = getIntAttribute(node, + "textBackgroundColor", + -1, true, + true, 0, 255); + + // XXX The "text" attribute of the PlainTextExtension element + // is not defined in the GIF image metadata format but it is + // present in the GIFImageMetadata class. Consequently it is + // used here but not required and with a default of "". See + // bug 5082763. + + String textString = + getStringAttribute(node, "text", "", false, null); + text = fromISO8859(textString); + } else if (name.equals("ApplicationExtensions")) { + IIOMetadataNode applicationExtension = + (IIOMetadataNode)node.getFirstChild(); + + if (!applicationExtension.getNodeName().equals("ApplicationExtension")) { + fatal(node, + "Only a ApplicationExtension may be a child of a ApplicationExtensions!"); + } + + String applicationIDString = + getStringAttribute(applicationExtension, "applicationID", + null, true, null); + + String authenticationCodeString = + getStringAttribute(applicationExtension, "authenticationCode", + null, true, null); + + Object applicationExtensionData = + applicationExtension.getUserObject(); + if (applicationExtensionData == null || + !(applicationExtensionData instanceof byte[])) { + fatal(applicationExtension, + "Bad user object in ApplicationExtension!"); + } + + if (applicationIDs == null) { + applicationIDs = new ArrayList(); + authenticationCodes = new ArrayList(); + applicationData = new ArrayList(); + } + + applicationIDs.add(fromISO8859(applicationIDString)); + authenticationCodes.add(fromISO8859(authenticationCodeString)); + applicationData.add(applicationExtensionData); + } else if (name.equals("CommentExtensions")) { + Node commentExtension = node.getFirstChild(); + if (commentExtension != null) { + while(commentExtension != null) { + if (!commentExtension.getNodeName().equals("CommentExtension")) { + fatal(node, + "Only a CommentExtension may be a child of a CommentExtensions!"); + } + + if (comments == null) { + comments = new ArrayList(); + } + + String comment = + getStringAttribute(commentExtension, "value", null, + true, null); + + comments.add(fromISO8859(comment)); + + commentExtension = commentExtension.getNextSibling(); + } + } + } else { + fatal(node, "Unknown child of root node!"); + } + + node = node.getNextSibling(); + } + } + + protected void mergeStandardTree(Node root) + throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName() + .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + fatal(node, "Root must be " + + IIOMetadataFormatImpl.standardMetadataFormatName); + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("Chroma")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("Palette")) { + localColorTable = getColorTable(childNode, + "PaletteEntry", + false, -1); + break; + } + childNode = childNode.getNextSibling(); + } + } else if (name.equals("Compression")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("NumProgressiveScans")) { + int numProgressiveScans = + getIntAttribute(childNode, "value", 4, false, + true, 1, Integer.MAX_VALUE); + if (numProgressiveScans > 1) { + interlaceFlag = true; + } + break; + } + childNode = childNode.getNextSibling(); + } + } else if (name.equals("Dimension")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("HorizontalPixelOffset")) { + imageLeftPosition = getIntAttribute(childNode, + "value", + -1, true, + true, 0, 65535); + } else if (childName.equals("VerticalPixelOffset")) { + imageTopPosition = getIntAttribute(childNode, + "value", + -1, true, + true, 0, 65535); + } + childNode = childNode.getNextSibling(); + } + } else if (name.equals("Text")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("TextEntry") && + getAttribute(childNode, "compression", + "none", false).equals("none") && + Charset.isSupported(getAttribute(childNode, + "encoding", + "ISO-8859-1", + false))) { + String value = getAttribute(childNode, "value"); + byte[] comment = fromISO8859(value); + if (comments == null) { + comments = new ArrayList(); + } + comments.add(comment); + } + childNode = childNode.getNextSibling(); + } + } else if (name.equals("Transparency")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("TransparentIndex")) { + transparentColorIndex = getIntAttribute(childNode, + "value", + -1, true, + true, 0, 255); + transparentColorFlag = true; + break; + } + childNode = childNode.getNextSibling(); + } + } + + node = node.getNextSibling(); + } + } + + public void setFromTree(String formatName, Node root) + throws IIOInvalidTreeException + { + reset(); + mergeTree(formatName, root); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFWritableStreamMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFWritableStreamMetadata.java new file mode 100644 index 000000000..8a9a6d865 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/gif/GIFWritableStreamMetadata.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.gif; + +/* + * The source for this class was copied verbatim from the source for + * package com.sun.imageio.plugins.gif.GIFImageMetadata and then modified + * to make the class read-write capable. + */ + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import org.w3c.dom.Node; + +class GIFWritableStreamMetadata extends GIFStreamMetadata { + + // package scope + static final String + NATIVE_FORMAT_NAME = "javax_imageio_gif_stream_1.0"; + + public GIFWritableStreamMetadata() { + super(true, + NATIVE_FORMAT_NAME, + "com.sun.imageio.plugins.gif.GIFStreamMetadataFormat", // XXX J2SE + null, null); + + // initialize metadata fields by default values + reset(); + } + + public boolean isReadOnly() { + return false; + } + + public void mergeTree(String formatName, Node root) + throws IIOInvalidTreeException { + if (formatName.equals(nativeMetadataFormatName)) { + if (root == null) { + throw new IllegalArgumentException("root == null!"); + } + mergeNativeTree(root); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + if (root == null) { + throw new IllegalArgumentException("root == null!"); + } + mergeStandardTree(root); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + public void reset() { + version = null; + + logicalScreenWidth = UNDEFINED_INTEGER_VALUE; + logicalScreenHeight = UNDEFINED_INTEGER_VALUE; + colorResolution = UNDEFINED_INTEGER_VALUE; + pixelAspectRatio = 0; + + backgroundColorIndex = 0; + sortFlag = false; + globalColorTable = null; + } + + protected void mergeNativeTree(Node root) throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName().equals(nativeMetadataFormatName)) { + fatal(node, "Root must be " + nativeMetadataFormatName); + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("Version")) { + version = getStringAttribute(node, "value", null, + true, versionStrings); + } else if (name.equals("LogicalScreenDescriptor")) { + /* NB: At the moment we use empty strings to support undefined + * integer values in tree representation. + * We need to add better support for undefined/default values + * later. + */ + logicalScreenWidth = getIntAttribute(node, + "logicalScreenWidth", + UNDEFINED_INTEGER_VALUE, + true, + true, 1, 65535); + + logicalScreenHeight = getIntAttribute(node, + "logicalScreenHeight", + UNDEFINED_INTEGER_VALUE, + true, + true, 1, 65535); + + colorResolution = getIntAttribute(node, + "colorResolution", + UNDEFINED_INTEGER_VALUE, + true, + true, 1, 8); + + pixelAspectRatio = getIntAttribute(node, + "pixelAspectRatio", + 0, true, + true, 0, 255); + } else if (name.equals("GlobalColorTable")) { + int sizeOfGlobalColorTable = + getIntAttribute(node, "sizeOfGlobalColorTable", + true, 2, 256); + if (sizeOfGlobalColorTable != 2 && + sizeOfGlobalColorTable != 4 && + sizeOfGlobalColorTable != 8 && + sizeOfGlobalColorTable != 16 && + sizeOfGlobalColorTable != 32 && + sizeOfGlobalColorTable != 64 && + sizeOfGlobalColorTable != 128 && + sizeOfGlobalColorTable != 256) { + fatal(node, + "Bad value for GlobalColorTable attribute sizeOfGlobalColorTable!"); + } + + backgroundColorIndex = getIntAttribute(node, + "backgroundColorIndex", + 0, true, + true, 0, 255); + + sortFlag = getBooleanAttribute(node, "sortFlag", false, true); + + globalColorTable = getColorTable(node, "ColorTableEntry", + true, sizeOfGlobalColorTable); + } else { + fatal(node, "Unknown child of root node!"); + } + + node = node.getNextSibling(); + } + } + + protected void mergeStandardTree(Node root) + throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName() + .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + fatal(node, "Root must be " + + IIOMetadataFormatImpl.standardMetadataFormatName); + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("Chroma")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("Palette")) { + globalColorTable = getColorTable(childNode, + "PaletteEntry", + false, -1); + + } else if (childName.equals("BackgroundIndex")) { + backgroundColorIndex = getIntAttribute(childNode, + "value", + -1, true, + true, 0, 255); + } + childNode = childNode.getNextSibling(); + } + } else if (name.equals("Data")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("BitsPerSample")) { + colorResolution = getIntAttribute(childNode, + "value", + -1, true, + true, 1, 8); + break; + } + childNode = childNode.getNextSibling(); + } + } else if (name.equals("Dimension")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("PixelAspectRatio")) { + float aspectRatio = getFloatAttribute(childNode, + "value"); + if (aspectRatio == 1.0F) { + pixelAspectRatio = 0; + } else { + int ratio = (int)(aspectRatio*64.0F - 15.0F); + pixelAspectRatio = + Math.max(Math.min(ratio, 255), 0); + } + } else if (childName.equals("HorizontalScreenSize")) { + logicalScreenWidth = getIntAttribute(childNode, + "value", + -1, true, + true, 1, 65535); + } else if (childName.equals("VerticalScreenSize")) { + logicalScreenHeight = getIntAttribute(childNode, + "value", + -1, true, + true, 1, 65535); + } + childNode = childNode.getNextSibling(); + } + } else if (name.equals("Document")) { + Node childNode = node.getFirstChild(); + while(childNode != null) { + String childName = childNode.getNodeName(); + if (childName.equals("FormatVersion")) { + String formatVersion = + getStringAttribute(childNode, "value", null, + true, null); + for (int i = 0; i < versionStrings.length; i++) { + if (formatVersion.equals(versionStrings[i])) { + version = formatVersion; + break; + } + } + break; + } + childNode = childNode.getNextSibling(); + } + } + + node = node.getNextSibling(); + } + } + + public void setFromTree(String formatName, Node root) + throws IIOInvalidTreeException + { + reset(); + mergeTree(formatName, root); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/AdobeMarkerSegment.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/AdobeMarkerSegment.java new file mode 100644 index 000000000..f7063bdc2 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/AdobeMarkerSegment.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.IIOException; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; + +import java.io.IOException; + +import org.w3c.dom.Node; +import org.w3c.dom.NamedNodeMap; + +/** + * An Adobe APP14 (Application-Specific) marker segment. + */ +class AdobeMarkerSegment extends MarkerSegment { + int version; + int flags0; + int flags1; + int transform; + private static final int ID_SIZE = 5; + + AdobeMarkerSegment(int transform) { + super(JPEG.APP14); + version = 101; + flags0 = 0; + flags1 = 0; + this.transform = transform; + } + + AdobeMarkerSegment(JPEGBuffer buffer) throws IOException { + super(buffer); + buffer.bufPtr += ID_SIZE; // Skip the id + version = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; + version |= buffer.buf[buffer.bufPtr++] & 0xff; + flags0 = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; + flags0 |= buffer.buf[buffer.bufPtr++] & 0xff; + flags1 = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; + flags1 |= buffer.buf[buffer.bufPtr++] & 0xff; + transform = buffer.buf[buffer.bufPtr++] & 0xff; + buffer.bufAvail -= length; + } + + AdobeMarkerSegment(Node node) throws IIOInvalidTreeException { + this(0); // default transform will be changed + updateFromNativeNode(node, true); + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("app14Adobe"); + node.setAttribute("version", Integer.toString(version)); + node.setAttribute("flags0", Integer.toString(flags0)); + node.setAttribute("flags1", Integer.toString(flags1)); + node.setAttribute("transform", Integer.toString(transform)); + + return node; + } + + void updateFromNativeNode(Node node, boolean fromScratch) + throws IIOInvalidTreeException { + // Only the transform is required + NamedNodeMap attrs = node.getAttributes(); + transform = getAttributeValue(node, attrs, "transform", 0, 2, true); + int count = attrs.getLength(); + if (count > 4) { + throw new IIOInvalidTreeException + ("Adobe APP14 node cannot have > 4 attributes", node); + } + if (count > 1) { + int value = getAttributeValue(node, attrs, "version", + 100, 255, false); + version = (value != -1) ? value : version; + value = getAttributeValue(node, attrs, "flags0", 0, 65535, false); + flags0 = (value != -1) ? value : flags0; + value = getAttributeValue(node, attrs, "flags1", 0, 65535, false); + flags1 = (value != -1) ? value : flags1; + } + } + + /** + * Writes the data for this segment to the stream in + * valid JPEG format. + */ + void write(ImageOutputStream ios) throws IOException { + length = 14; + writeTag(ios); + byte [] id = {0x41, 0x64, 0x6F, 0x62, 0x65}; + ios.write(id); + write2bytes(ios, version); + write2bytes(ios, flags0); + write2bytes(ios, flags1); + ios.write(transform); + } + + static void writeAdobeSegment(ImageOutputStream ios, int transform) + throws IOException { + (new AdobeMarkerSegment(transform)).write(ios); + } + + void print () { + printTag("Adobe APP14"); + System.out.print("Version: "); + System.out.println(version); + System.out.print("Flags0: 0x"); + System.out.println(Integer.toHexString(flags0)); + System.out.print("Flags1: 0x"); + System.out.println(Integer.toHexString(flags1)); + System.out.print("Transform: "); + System.out.println(transform); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/COMMarkerSegment.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/COMMarkerSegment.java new file mode 100644 index 000000000..c53cf35c8 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/COMMarkerSegment.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.metadata.IIOInvalidTreeException; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import org.w3c.dom.Node; + +/** + * A Comment marker segment. Retains an array of bytes representing the + * comment data as it is read from the stream. If the marker segment is + * constructed from a String, then local default encoding is assumed + * when creating the byte array. If the marker segment is created from + * an IIOMetadataNode, the user object, if present is + * assumed to be a byte array containing the comment data. If there is + * no user object then the comment attribute is used to create the + * byte array, again assuming the default local encoding. + */ +class COMMarkerSegment extends MarkerSegment { + private static final String ENCODING = "ISO-8859-1"; + + /** + * Constructs a marker segment from the given buffer, which contains + * data from an ImageInputStream. This is used when + * reading metadata from a stream. + */ + COMMarkerSegment(JPEGBuffer buffer) throws IOException { + super(buffer); + loadData(buffer); + } + + /** + * Constructs a marker segment from a String. This is used when + * modifying metadata from a non-native tree and when transcoding. + * The default encoding is used to construct the byte array. + */ + COMMarkerSegment(String comment) { + super(JPEG.COM); + data = comment.getBytes(); // Default encoding + } + + /** + * Constructs a marker segment from a native tree node. If the node + * is an IIOMetadataNode and contains a user object, + * that object is used rather than the string attribute. If the + * string attribute is used, the default encoding is used. + */ + COMMarkerSegment(Node node) throws IIOInvalidTreeException{ + super(JPEG.COM); + if (node instanceof IIOMetadataNode) { + IIOMetadataNode ourNode = (IIOMetadataNode) node; + data = (byte []) ourNode.getUserObject(); + } + if (data == null) { + String comment = + node.getAttributes().getNamedItem("comment").getNodeValue(); + if (comment != null) { + data = comment.getBytes(); // Default encoding + } else { + throw new IIOInvalidTreeException("Empty comment node!", node); + } + } + } + + /** + * Returns the array encoded as a String, using ISO-Latin-1 encoding. + * If an application needs another encoding, the data array must be + * consulted directly. + */ + String getComment() { + try { + return new String (data, ENCODING); + } catch (UnsupportedEncodingException e) {} // Won't happen + return null; + } + + /** + * Returns an IIOMetadataNode containing the data array + * as a user object and a string encoded using ISO-8895-1, as an + * attribute. + */ + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("com"); + node.setAttribute("comment", getComment()); + if (data != null) { + node.setUserObject(data.clone()); + } + return node; + } + + /** + * Writes the data for this segment to the stream in + * valid JPEG format, directly from the data array. + */ + void write(ImageOutputStream ios) throws IOException { + length = 2 + data.length; + writeTag(ios); + ios.write(data); + } + + void print() { + printTag("COM"); + System.out.println("<" + getComment() + ">"); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/DHTMarkerSegment.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/DHTMarkerSegment.java new file mode 100644 index 000000000..f619bb1d4 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/DHTMarkerSegment.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.plugins.jpeg.JPEGHuffmanTable; + +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.NamedNodeMap; + +/** + * A DHT (Define Huffman Table) marker segment. + */ +class DHTMarkerSegment extends MarkerSegment { + List tables = new ArrayList(); + + DHTMarkerSegment(boolean needFour) { + super(JPEG.DHT); + tables.add(new Htable(JPEGHuffmanTable.StdDCLuminance, true, 0)); + if (needFour) { + tables.add(new Htable(JPEGHuffmanTable.StdDCChrominance, true, 1)); + } + tables.add(new Htable(JPEGHuffmanTable.StdACLuminance, false, 0)); + if (needFour) { + tables.add(new Htable(JPEGHuffmanTable.StdACChrominance, false, 1)); + } + } + + DHTMarkerSegment(JPEGBuffer buffer) throws IOException { + super(buffer); + int count = length; + while (count > 0) { + Htable newGuy = new Htable(buffer); + tables.add(newGuy); + count -= 1 + 16 + newGuy.values.length; + } + buffer.bufAvail -= length; + } + + DHTMarkerSegment(JPEGHuffmanTable[] dcTables, + JPEGHuffmanTable[] acTables) { + super(JPEG.DHT); + for (int i = 0; i < dcTables.length; i++) { + tables.add(new Htable(dcTables[i], true, i)); + } + for (int i = 0; i < acTables.length; i++) { + tables.add(new Htable(acTables[i], false, i)); + } + } + + DHTMarkerSegment(Node node) throws IIOInvalidTreeException { + super(JPEG.DHT); + NodeList children = node.getChildNodes(); + int size = children.getLength(); + if ((size < 1) || (size > 4)) { + throw new IIOInvalidTreeException("Invalid DHT node", node); + } + for (int i = 0; i < size; i++) { + tables.add(new Htable(children.item(i))); + } + } + + protected Object clone() { + DHTMarkerSegment newGuy = (DHTMarkerSegment) super.clone(); + newGuy.tables = new ArrayList(tables.size()); + Iterator iter = tables.iterator(); + while (iter.hasNext()) { + Htable table = (Htable) iter.next(); + newGuy.tables.add(table.clone()); + } + return newGuy; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("dht"); + for (int i= 0; i>> 4; + tableID = buffer.buf[buffer.bufPtr++] & 0xf; + for (int i = 0; i < NUM_LENGTHS; i++) { + numCodes[i] = (short) (buffer.buf[buffer.bufPtr++] & 0xff); + } + + int numValues = 0; + for (int i = 0; i < NUM_LENGTHS; i++) { + numValues += numCodes[i]; + } + values = new short[numValues]; + for (int i = 0; i < numValues; i++) { + values[i] = (short) (buffer.buf[buffer.bufPtr++] & 0xff); + } + } + + Htable(JPEGHuffmanTable table, boolean isDC, int id) { + tableClass = isDC ? 0 : 1; + tableID = id; + numCodes = table.getLengths(); + values = table.getValues(); + } + + Htable(Node node) throws IIOInvalidTreeException { + if (node.getNodeName().equals("dhtable")) { + NamedNodeMap attrs = node.getAttributes(); + int count = attrs.getLength(); + if (count != 2) { + throw new IIOInvalidTreeException + ("dhtable node must have 2 attributes", node); + } + tableClass = getAttributeValue(node, attrs, "class", 0, 1, true); + tableID = getAttributeValue(node, attrs, "htableId", 0, 3, true); + if (node instanceof IIOMetadataNode) { + IIOMetadataNode ourNode = (IIOMetadataNode) node; + JPEGHuffmanTable table = + (JPEGHuffmanTable) ourNode.getUserObject(); + if (table == null) { + throw new IIOInvalidTreeException + ("dhtable node must have user object", node); + } + numCodes = table.getLengths(); + values = table.getValues(); + } else { + throw new IIOInvalidTreeException + ("dhtable node must have user object", node); + } + } else { + throw new IIOInvalidTreeException + ("Invalid node, expected dqtable", node); + } + + } + + protected Object clone() { + Htable newGuy = null; + try { + newGuy = (Htable) super.clone(); + } catch (CloneNotSupportedException e) {} // won't happen + if (numCodes != null) { + newGuy.numCodes = (short []) numCodes.clone(); + } + if (values != null) { + newGuy.values = (short []) values.clone(); + } + return newGuy; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("dhtable"); + node.setAttribute("class", Integer.toString(tableClass)); + node.setAttribute("htableId", Integer.toString(tableID)); + + node.setUserObject(new JPEGHuffmanTable(numCodes, values)); + + return node; + } + + + void print() { + System.out.println("Huffman Table"); + System.out.println("table class: " + + ((tableClass == 0) ? "DC":"AC")); + System.out.println("table id: " + Integer.toString(tableID)); + + (new JPEGHuffmanTable(numCodes, values)).toString(); + /* + System.out.print("Lengths:"); + for (int i=0; i<16; i++) { + System.out.print(" " + Integer.toString(numCodes[i])); + } + int count = 0; + if (values.length > 16) { + System.out.println("\nFirst 16 Values:"); + count = 16; + } else { + System.out.println("\nValues:"); + count = values.length; + } + for (int i=0; i 0) { + Qtable newGuy = new Qtable(buffer); + tables.add(newGuy); + count -= newGuy.data.length+1; + } + buffer.bufAvail -= length; + } + + DQTMarkerSegment(JPEGQTable[] qtables) { + super(JPEG.DQT); + for (int i = 0; i < qtables.length; i++) { + tables.add(new Qtable(qtables[i], i)); + } + } + + DQTMarkerSegment(Node node) throws IIOInvalidTreeException { + super(JPEG.DQT); + NodeList children = node.getChildNodes(); + int size = children.getLength(); + if ((size < 1) || (size > 4)) { + throw new IIOInvalidTreeException("Invalid DQT node", node); + } + for (int i = 0; i < size; i++) { + tables.add(new Qtable(children.item(i))); + } + } + + protected Object clone() { + DQTMarkerSegment newGuy = (DQTMarkerSegment) super.clone(); + newGuy.tables = new ArrayList(tables.size()); + Iterator iter = tables.iterator(); + while (iter.hasNext()) { + Qtable table = (Qtable) iter.next(); + newGuy.tables.add(table.clone()); + } + return newGuy; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("dqt"); + for (int i= 0; i luma.data[largestPos]) { + largestPos = i; + } + } + // Compute the scale factor by dividing it by the value in the + // same position from the "standard" table. + // If the given table was not generated by scaling the standard, + // the resulting table will still be reasonable, as it will reflect + // a comparable scaling of chrominance frequency response of the + // eye. + float scaleFactor = ((float)(luma.data[largestPos])) + / ((float)(JPEGQTable.K1Div2Luminance.getTable()[largestPos])); + // generate a new table + JPEGQTable jpegTable = + JPEGQTable.K2Div2Chrominance.getScaledInstance(scaleFactor, + true); + newGuy = new Qtable(jpegTable, 1); + } + return newGuy; + } + + Qtable getQtableFromNode(Node node) throws IIOInvalidTreeException { + return new Qtable(node); + } + + /** + * A quantization table within a DQT marker segment. + */ + class Qtable implements Cloneable { + int elementPrecision; + int tableID; + final int QTABLE_SIZE = 64; + int [] data; // 64 elements, in natural order + + /** + * The zigzag-order position of the i'th element + * of a DCT block read in natural order. + */ + private final int [] zigzag = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 + }; + + Qtable(boolean wantLuma, float quality) { + elementPrecision = 0; + JPEGQTable base = null; + if (wantLuma) { + tableID = 0; + base = JPEGQTable.K1Div2Luminance; + } else { + tableID = 1; + base = JPEGQTable.K2Div2Chrominance; + } + if (quality != JPEG.DEFAULT_QUALITY) { + quality = JPEG.convertToLinearQuality(quality); + if (wantLuma) { + base = JPEGQTable.K1Luminance.getScaledInstance + (quality, true); + } else { + base = JPEGQTable.K2Div2Chrominance.getScaledInstance + (quality, true); + } + } + data = base.getTable(); + } + + Qtable(JPEGBuffer buffer) throws IIOException { + elementPrecision = buffer.buf[buffer.bufPtr] >>> 4; + tableID = buffer.buf[buffer.bufPtr++] & 0xf; + if (elementPrecision != 0) { + // IJG is compiled for 8-bits, so this shouldn't happen + throw new IIOException ("Unsupported element precision"); + } + data = new int [QTABLE_SIZE]; + // Read from zig-zag order to natural order + for (int i = 0; i < QTABLE_SIZE; i++) { + data[i] = buffer.buf[buffer.bufPtr+zigzag[i]] & 0xff; + } + buffer.bufPtr += QTABLE_SIZE; + } + + Qtable(JPEGQTable table, int id) { + elementPrecision = 0; + tableID = id; + data = table.getTable(); + } + + Qtable(Node node) throws IIOInvalidTreeException { + if (node.getNodeName().equals("dqtable")) { + NamedNodeMap attrs = node.getAttributes(); + int count = attrs.getLength(); + if ((count < 1) || (count > 2)) { + throw new IIOInvalidTreeException + ("dqtable node must have 1 or 2 attributes", node); + } + elementPrecision = 0; + tableID = getAttributeValue(node, attrs, "qtableId", 0, 3, true); + if (node instanceof IIOMetadataNode) { + IIOMetadataNode ourNode = (IIOMetadataNode) node; + JPEGQTable table = (JPEGQTable) ourNode.getUserObject(); + if (table == null) { + throw new IIOInvalidTreeException + ("dqtable node must have user object", node); + } + data = table.getTable(); + } else { + throw new IIOInvalidTreeException + ("dqtable node must have user object", node); + } + } else { + throw new IIOInvalidTreeException + ("Invalid node, expected dqtable", node); + } + } + + protected Object clone() { + Qtable newGuy = null; + try { + newGuy = (Qtable) super.clone(); + } catch (CloneNotSupportedException e) {} // won't happen + if (data != null) { + newGuy.data = (int []) data.clone(); + } + return newGuy; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("dqtable"); + node.setAttribute("elementPrecision", + Integer.toString(elementPrecision)); + node.setAttribute("qtableId", + Integer.toString(tableID)); + node.setUserObject(new JPEGQTable(data)); + return node; + } + + void print() { + System.out.println("Table id: " + Integer.toString(tableID)); + System.out.println("Element precision: " + + Integer.toString(elementPrecision)); + + (new JPEGQTable(data)).toString(); + /* + for (int i = 0; i < 64; i++) { + if (i % 8 == 0) { + System.out.println(); + } + System.out.print(" " + Integer.toString(data[i])); + } + System.out.println(); + */ + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/DRIMarkerSegment.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/DRIMarkerSegment.java new file mode 100644 index 000000000..8033fbd2d --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/DRIMarkerSegment.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; + +import java.io.IOException; + +import org.w3c.dom.Node; + +/** + * A DRI (Define Restart Interval) marker segment. + */ +class DRIMarkerSegment extends MarkerSegment { + /** + * Restart interval, or 0 if none is specified. + */ + int restartInterval = 0; + + DRIMarkerSegment(JPEGBuffer buffer) + throws IOException { + super(buffer); + restartInterval = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; + restartInterval |= buffer.buf[buffer.bufPtr++] & 0xff; + buffer.bufAvail -= length; + } + + DRIMarkerSegment(Node node) throws IIOInvalidTreeException { + super(JPEG.DRI); + updateFromNativeNode(node, true); + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("dri"); + node.setAttribute("interval", Integer.toString(restartInterval)); + return node; + } + + void updateFromNativeNode(Node node, boolean fromScratch) + throws IIOInvalidTreeException { + restartInterval = getAttributeValue(node, null, "interval", + 0, 65535, true); + } + + /** + * Writes the data for this segment to the stream in + * valid JPEG format. + */ + void write(ImageOutputStream ios) throws IOException { + // We don't write DRI segments; the IJG library does. + } + + void print() { + printTag("DRI"); + System.out.println("Interval: " + + Integer.toString(restartInterval)); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JFIFMarkerSegment.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JFIFMarkerSegment.java new file mode 100644 index 000000000..3daf4bed4 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JFIFMarkerSegment.java @@ -0,0 +1,1564 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageReader; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import javax.imageio.event.IIOReadProgressListener; + +import java.awt.Graphics; +import java.awt.color.ICC_Profile; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.image.SampleModel; +import java.awt.image.IndexColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.NamedNodeMap; + +/** + * A JFIF (JPEG File Interchange Format) APP0 (Application-Specific) + * marker segment. Inner classes are included for JFXX extension + * marker segments, for different varieties of thumbnails, and for + * ICC Profile APP2 marker segments. Any of these secondary types + * that occur are kept as members of a single JFIFMarkerSegment object. + */ +class JFIFMarkerSegment extends MarkerSegment { + int majorVersion; + int minorVersion; + int resUnits; + int Xdensity; + int Ydensity; + int thumbWidth; + int thumbHeight; + JFIFThumbRGB thumb = null; // If present + ArrayList extSegments = new ArrayList(); + ICCMarkerSegment iccSegment = null; // optional ICC + private static final int THUMB_JPEG = 0x10; + private static final int THUMB_PALETTE = 0x11; + private static final int THUMB_UNASSIGNED = 0x12; + private static final int THUMB_RGB = 0x13; + private static final int DATA_SIZE = 14; + private static final int ID_SIZE = 5; + private final int MAX_THUMB_WIDTH = 255; + private final int MAX_THUMB_HEIGHT = 255; + + private final boolean debug = false; + + /** + * Set to true when reading the chunks of an + * ICC profile. All chunks are consolidated to create a single + * "segment" containing all the chunks. This flag is a state + * variable identifying whether to construct a new segment or + * append to an old one. + */ + private boolean inICC = false; + + /** + * A placeholder for an ICC profile marker segment under + * construction. The segment is not added to the list + * until all chunks have been read. + */ + private ICCMarkerSegment tempICCSegment = null; + + + /** + * Default constructor. Used to create a default JFIF header + */ + JFIFMarkerSegment() { + super(JPEG.APP0); + majorVersion = 1; + minorVersion = 2; + resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO; + Xdensity = 1; + Ydensity = 1; + thumbWidth = 0; + thumbHeight = 0; + } + + /** + * Constructs a JFIF header by reading from a stream wrapped + * in a JPEGBuffer. + */ + JFIFMarkerSegment(JPEGBuffer buffer) throws IOException { + super(buffer); + buffer.bufPtr += ID_SIZE; // skip the id, we already checked it + + majorVersion = buffer.buf[buffer.bufPtr++]; + minorVersion = buffer.buf[buffer.bufPtr++]; + resUnits = buffer.buf[buffer.bufPtr++]; + Xdensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; + Xdensity |= buffer.buf[buffer.bufPtr++] & 0xff; + Ydensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; + Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff; + thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff; + thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff; + buffer.bufAvail -= DATA_SIZE; + if (thumbWidth > 0) { + thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight); + } + } + + /** + * Constructs a JFIF header from a DOM Node. + */ + JFIFMarkerSegment(Node node) throws IIOInvalidTreeException { + this(); + updateFromNativeNode(node, true); + } + + /** + * Returns a deep-copy clone of this object. + */ + protected Object clone() { + JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone(); + if (!extSegments.isEmpty()) { // Clone the list with a deep copy + newGuy.extSegments = new ArrayList(); + for (Iterator iter = extSegments.iterator(); iter.hasNext();) { + JFIFExtensionMarkerSegment jfxx = + (JFIFExtensionMarkerSegment) iter.next(); + newGuy.extSegments.add(jfxx.clone()); + } + } + if (iccSegment != null) { + newGuy.iccSegment = (ICCMarkerSegment) iccSegment.clone(); + } + return newGuy; + } + + /** + * Add an JFXX extension marker segment from the stream wrapped + * in the JPEGBuffer to the list of extension segments. + */ + void addJFXX(JPEGBuffer buffer, JPEGImageReader reader) + throws IOException { + extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader)); + } + + /** + * Adds an ICC Profile APP2 segment from the stream wrapped + * in the JPEGBuffer. + */ + void addICC(JPEGBuffer buffer) throws IOException { + if (inICC == false) { + if (iccSegment != null) { + throw new IIOException + ("> 1 ICC APP2 Marker Segment not supported"); + } + tempICCSegment = new ICCMarkerSegment(buffer); + if (inICC == false) { // Just one chunk + iccSegment = tempICCSegment; + tempICCSegment = null; + } + } else { + if (tempICCSegment.addData(buffer) == true) { + iccSegment = tempICCSegment; + tempICCSegment = null; + } + } + } + + /** + * Add an ICC Profile APP2 segment by constructing it from + * the given ICC_ColorSpace object. + */ + void addICC(ICC_ColorSpace cs) throws IOException { + if (iccSegment != null) { + throw new IIOException + ("> 1 ICC APP2 Marker Segment not supported"); + } + iccSegment = new ICCMarkerSegment(cs); + } + + /** + * Returns a tree of DOM nodes representing this object and any + * subordinate JFXX extension or ICC Profile segments. + */ + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("app0JFIF"); + node.setAttribute("majorVersion", Integer.toString(majorVersion)); + node.setAttribute("minorVersion", Integer.toString(minorVersion)); + node.setAttribute("resUnits", Integer.toString(resUnits)); + node.setAttribute("Xdensity", Integer.toString(Xdensity)); + node.setAttribute("Ydensity", Integer.toString(Ydensity)); + node.setAttribute("thumbWidth", Integer.toString(thumbWidth)); + node.setAttribute("thumbHeight", Integer.toString(thumbHeight)); + if (!extSegments.isEmpty()) { + IIOMetadataNode JFXXnode = new IIOMetadataNode("JFXX"); + node.appendChild(JFXXnode); + for (Iterator iter = extSegments.iterator(); iter.hasNext();) { + JFIFExtensionMarkerSegment seg = + (JFIFExtensionMarkerSegment) iter.next(); + JFXXnode.appendChild(seg.getNativeNode()); + } + } + if (iccSegment != null) { + node.appendChild(iccSegment.getNativeNode()); + } + + return node; + } + + /** + * Updates the data in this object from the given DOM Node tree. + * If fromScratch is true, this object is being constructed. + * Otherwise an existing object is being modified. + * Throws an IIOInvalidTreeException if the tree is invalid in + * any way. + */ + void updateFromNativeNode(Node node, boolean fromScratch) + throws IIOInvalidTreeException { + // none of the attributes are required + NamedNodeMap attrs = node.getAttributes(); + if (attrs.getLength() > 0) { + int value = getAttributeValue(node, attrs, "majorVersion", + 0, 255, false); + majorVersion = (value != -1) ? value : majorVersion; + value = getAttributeValue(node, attrs, "minorVersion", + 0, 255, false); + minorVersion = (value != -1) ? value : minorVersion; + value = getAttributeValue(node, attrs, "resUnits", 0, 2, false); + resUnits = (value != -1) ? value : resUnits; + value = getAttributeValue(node, attrs, "Xdensity", 1, 65535, false); + Xdensity = (value != -1) ? value : Xdensity; + value = getAttributeValue(node, attrs, "Ydensity", 1, 65535, false); + Ydensity = (value != -1) ? value : Ydensity; + value = getAttributeValue(node, attrs, "thumbWidth", 0, 255, false); + thumbWidth = (value != -1) ? value : thumbWidth; + value = getAttributeValue(node, attrs, "thumbHeight", 0, 255, false); + thumbHeight = (value != -1) ? value : thumbHeight; + } + if (node.hasChildNodes()) { + NodeList children = node.getChildNodes(); + int count = children.getLength(); + if (count > 2) { + throw new IIOInvalidTreeException + ("app0JFIF node cannot have > 2 children", node); + } + for (int i = 0; i < count; i++) { + Node child = children.item(i); + String name = child.getNodeName(); + if (name.equals("JFXX")) { + if ((!extSegments.isEmpty()) && fromScratch) { + throw new IIOInvalidTreeException + ("app0JFIF node cannot have > 1 JFXX node", node); + } + NodeList exts = child.getChildNodes(); + int extCount = exts.getLength(); + for (int j = 0; j < extCount; j++) { + Node ext = exts.item(j); + extSegments.add(new JFIFExtensionMarkerSegment(ext)); + } + } + if (name.equals("app2ICC")) { + if ((iccSegment != null) && fromScratch) { + throw new IIOInvalidTreeException + ("> 1 ICC APP2 Marker Segment not supported", node); + } + iccSegment = new ICCMarkerSegment(child); + } + } + } + } + + int getThumbnailWidth(int index) { + if (thumb != null) { + if (index == 0) { + return thumb.getWidth(); + } + index--; + } + JFIFExtensionMarkerSegment jfxx = + (JFIFExtensionMarkerSegment) extSegments.get(index); + return jfxx.thumb.getWidth(); + } + + int getThumbnailHeight(int index) { + if (thumb != null) { + if (index == 0) { + return thumb.getHeight(); + } + index--; + } + JFIFExtensionMarkerSegment jfxx = + (JFIFExtensionMarkerSegment) extSegments.get(index); + return jfxx.thumb.getHeight(); + } + + BufferedImage getThumbnail(ImageInputStream iis, + int index, + JPEGImageReader reader) throws IOException { + reader.thumbnailStarted(index); + BufferedImage ret = null; + if ((thumb != null) && (index == 0)) { + ret = thumb.getThumbnail(iis, reader); + } else { + if (thumb != null) { + index--; + } + JFIFExtensionMarkerSegment jfxx = + (JFIFExtensionMarkerSegment) extSegments.get(index); + ret = jfxx.thumb.getThumbnail(iis, reader); + } + reader.thumbnailComplete(); + return ret; + } + + + /** + * Writes the data for this segment to the stream in + * valid JPEG format. Assumes that there will be no thumbnail. + */ + void write(ImageOutputStream ios, + JPEGImageWriter writer) throws IOException { + // No thumbnail + write(ios, null, writer); + } + + /** + * Writes the data for this segment to the stream in + * valid JPEG format. The length written takes the thumbnail + * width and height into account. If necessary, the thumbnail + * is clipped to 255 x 255 and a warning is sent to the writer + * argument. Progress updates are sent to the writer argument. + */ + void write(ImageOutputStream ios, + BufferedImage thumb, + JPEGImageWriter writer) throws IOException { + int thumbWidth = 0; + int thumbHeight = 0; + int thumbLength = 0; + int [] thumbData = null; + if (thumb != null) { + // Clip if necessary and get the data in thumbData + thumbWidth = thumb.getWidth(); + thumbHeight = thumb.getHeight(); + if ((thumbWidth > MAX_THUMB_WIDTH) + || (thumbHeight > MAX_THUMB_HEIGHT)) { + writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED); + } + thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH); + thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT); + thumbData = thumb.getRaster().getPixels(0, 0, + thumbWidth, thumbHeight, + (int []) null); + thumbLength = thumbData.length; + } + length = DATA_SIZE + LENGTH_SIZE + thumbLength; + writeTag(ios); + byte [] id = {0x4A, 0x46, 0x49, 0x46, 0x00}; + ios.write(id); + ios.write(majorVersion); + ios.write(minorVersion); + ios.write(resUnits); + write2bytes(ios, Xdensity); + write2bytes(ios, Ydensity); + ios.write(thumbWidth); + ios.write(thumbHeight); + if (thumbData != null) { + writer.thumbnailStarted(0); + writeThumbnailData(ios, thumbData, writer); + writer.thumbnailComplete(); + } + } + + /* + * Write out the values in the integer array as a sequence of bytes, + * reporting progress to the writer argument. + */ + void writeThumbnailData(ImageOutputStream ios, + int [] thumbData, + JPEGImageWriter writer) throws IOException { + int progInterval = thumbData.length / 20; // approx. every 5% + if (progInterval == 0) { + progInterval = 1; + } + for (int i = 0; i < thumbData.length; i++) { + ios.write(thumbData[i]); + if ((i > progInterval) && (i % progInterval == 0)) { + writer.thumbnailProgress + (((float) i * 100) / ((float) thumbData.length)); + } + } + } + + /** + * Write out this JFIF Marker Segment, including a thumbnail or + * appending a series of JFXX Marker Segments, as appropriate. + * Warnings and progress reports are sent to the writer argument. + * The list of thumbnails is matched to the list of JFXX extension + * segments, if any, in order to determine how to encode the + * thumbnails. If there are more thumbnails than metadata segments, + * default encoding is used for the extra thumbnails. + */ + void writeWithThumbs(ImageOutputStream ios, + List thumbnails, + JPEGImageWriter writer) throws IOException { + if (thumbnails != null) { + JFIFExtensionMarkerSegment jfxx = null; + if (thumbnails.size() == 1) { + if (!extSegments.isEmpty()) { + jfxx = (JFIFExtensionMarkerSegment) extSegments.get(0); + } + writeThumb(ios, + (BufferedImage) thumbnails.get(0), + jfxx, + 0, + true, + writer); + } else { + // All others write as separate JFXX segments + write(ios, writer); // Just the header without any thumbnail + for (int i = 0; i < thumbnails.size(); i++) { + jfxx = null; + if (i < extSegments.size()) { + jfxx = (JFIFExtensionMarkerSegment) extSegments.get(i); + } + writeThumb(ios, + (BufferedImage) thumbnails.get(i), + jfxx, + i, + false, + writer); + } + } + } else { // No thumbnails + write(ios, writer); + } + + } + + private void writeThumb(ImageOutputStream ios, + BufferedImage thumb, + JFIFExtensionMarkerSegment jfxx, + int index, + boolean onlyOne, + JPEGImageWriter writer) throws IOException { + ColorModel cm = thumb.getColorModel(); + ColorSpace cs = cm.getColorSpace(); + + if (cm instanceof IndexColorModel) { + // We never write a palette image into the header + // So if it's the only one, we need to write the header first + if (onlyOne) { + write(ios, writer); + } + if ((jfxx == null) + || (jfxx.code == THUMB_PALETTE)) { + writeJFXXSegment(index, thumb, ios, writer); // default + } else { + // Expand to RGB + BufferedImage thumbRGB = + ((IndexColorModel) cm).convertToIntDiscrete + (thumb.getRaster(), false); + jfxx.setThumbnail(thumbRGB); + writer.thumbnailStarted(index); + jfxx.write(ios, writer); // Handles clipping if needed + writer.thumbnailComplete(); + } + } else if (cs.getType() == ColorSpace.TYPE_RGB) { + if (jfxx == null) { + if (onlyOne) { + write(ios, thumb, writer); // As part of the header + } else { + writeJFXXSegment(index, thumb, ios, writer); // default + } + } else { + // If this is the only one, write the header first + if (onlyOne) { + write(ios, writer); + } + if (jfxx.code == THUMB_PALETTE) { + writeJFXXSegment(index, thumb, ios, writer); // default + writer.warningOccurred + (JPEGImageWriter.WARNING_NO_RGB_THUMB_AS_INDEXED); + } else { + jfxx.setThumbnail(thumb); + writer.thumbnailStarted(index); + jfxx.write(ios, writer); // Handles clipping if needed + writer.thumbnailComplete(); + } + } + } else if (cs.getType() == ColorSpace.TYPE_GRAY) { + if (jfxx == null) { + if (onlyOne) { + BufferedImage thumbRGB = expandGrayThumb(thumb); + write(ios, thumbRGB, writer); // As part of the header + } else { + writeJFXXSegment(index, thumb, ios, writer); // default + } + } else { + // If this is the only one, write the header first + if (onlyOne) { + write(ios, writer); + } + if (jfxx.code == THUMB_RGB) { + BufferedImage thumbRGB = expandGrayThumb(thumb); + writeJFXXSegment(index, thumbRGB, ios, writer); + } else if (jfxx.code == THUMB_JPEG) { + jfxx.setThumbnail(thumb); + writer.thumbnailStarted(index); + jfxx.write(ios, writer); // Handles clipping if needed + writer.thumbnailComplete(); + } else if (jfxx.code == THUMB_PALETTE) { + writeJFXXSegment(index, thumb, ios, writer); // default + writer.warningOccurred + (JPEGImageWriter.WARNING_NO_GRAY_THUMB_AS_INDEXED); + } + } + } else { + writer.warningOccurred + (JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL); + } + } + + // Could put reason codes in here to be parsed in writeJFXXSegment + // in order to provide more meaningful warnings. + private class IllegalThumbException extends Exception {} + + /** + * Writes out a new JFXX extension segment, without saving it. + */ + private void writeJFXXSegment(int index, + BufferedImage thumbnail, + ImageOutputStream ios, + JPEGImageWriter writer) throws IOException { + JFIFExtensionMarkerSegment jfxx = null; + try { + jfxx = new JFIFExtensionMarkerSegment(thumbnail); + } catch (IllegalThumbException e) { + writer.warningOccurred + (JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL); + return; + } + writer.thumbnailStarted(index); + jfxx.write(ios, writer); + writer.thumbnailComplete(); + } + + + /** + * Return an RGB image that is the expansion of the given grayscale + * image. + */ + private static BufferedImage expandGrayThumb(BufferedImage thumb) { + BufferedImage ret = new BufferedImage(thumb.getWidth(), + thumb.getHeight(), + BufferedImage.TYPE_INT_RGB); + Graphics g = ret.getGraphics(); + g.drawImage(thumb, 0, 0, null); + return ret; + } + + /** + * Writes out a default JFIF marker segment to the given + * output stream. If thumbnails is not null, + * writes out the set of thumbnail images as JFXX marker segments, or + * incorporated into the JFIF segment if appropriate. + * If iccProfile is not null, + * writes out the profile after the JFIF segment using as many APP2 + * marker segments as necessary. + */ + static void writeDefaultJFIF(ImageOutputStream ios, + List thumbnails, + ICC_Profile iccProfile, + JPEGImageWriter writer) + throws IOException { + + JFIFMarkerSegment jfif = new JFIFMarkerSegment(); + jfif.writeWithThumbs(ios, thumbnails, writer); + if (iccProfile != null) { + writeICC(iccProfile, ios); + } + } + + /** + * Prints out the contents of this object to System.out for debugging. + */ + void print() { + printTag("JFIF"); + System.out.print("Version "); + System.out.print(majorVersion); + System.out.println(".0" + + Integer.toString(minorVersion)); + System.out.print("Resolution units: "); + System.out.println(resUnits); + System.out.print("X density: "); + System.out.println(Xdensity); + System.out.print("Y density: "); + System.out.println(Ydensity); + System.out.print("Thumbnail Width: "); + System.out.println(thumbWidth); + System.out.print("Thumbnail Height: "); + System.out.println(thumbHeight); + if (!extSegments.isEmpty()) { + for (Iterator iter = extSegments.iterator(); iter.hasNext();) { + JFIFExtensionMarkerSegment extSegment = + (JFIFExtensionMarkerSegment) iter.next(); + extSegment.print(); + } + } + if (iccSegment != null) { + iccSegment.print(); + } + } + + /** + * A JFIF extension APP0 marker segment. + */ + class JFIFExtensionMarkerSegment extends MarkerSegment { + int code; + JFIFThumb thumb; + private static final int DATA_SIZE = 6; + private static final int ID_SIZE = 5; + + JFIFExtensionMarkerSegment(JPEGBuffer buffer, JPEGImageReader reader) + throws IOException { + + super(buffer); + buffer.bufPtr += ID_SIZE; // skip the id, we already checked it + + code = buffer.buf[buffer.bufPtr++] & 0xff; + buffer.bufAvail -= DATA_SIZE; + if (code == THUMB_JPEG) { + thumb = new JFIFThumbJPEG(buffer, length, reader); + } else { + buffer.loadBuf(2); + int thumbX = buffer.buf[buffer.bufPtr++] & 0xff; + int thumbY = buffer.buf[buffer.bufPtr++] & 0xff; + buffer.bufAvail -= 2; + // following constructors handle bufAvail + if (code == THUMB_PALETTE) { + thumb = new JFIFThumbPalette(buffer, thumbX, thumbY); + } else { + thumb = new JFIFThumbRGB(buffer, thumbX, thumbY); + } + } + } + + JFIFExtensionMarkerSegment(Node node) throws IIOInvalidTreeException { + super(JPEG.APP0); + NamedNodeMap attrs = node.getAttributes(); + if (attrs.getLength() > 0) { + code = getAttributeValue(node, + attrs, + "extensionCode", + THUMB_JPEG, + THUMB_RGB, + false); + if (code == THUMB_UNASSIGNED) { + throw new IIOInvalidTreeException + ("invalid extensionCode attribute value", node); + } + } else { + code = THUMB_UNASSIGNED; + } + // Now the child + if (node.getChildNodes().getLength() != 1) { + throw new IIOInvalidTreeException + ("app0JFXX node must have exactly 1 child", node); + } + Node child = node.getFirstChild(); + String name = child.getNodeName(); + if (name.equals("JFIFthumbJPEG")) { + if (code == THUMB_UNASSIGNED) { + code = THUMB_JPEG; + } + thumb = new JFIFThumbJPEG(child); + } else if (name.equals("JFIFthumbPalette")) { + if (code == THUMB_UNASSIGNED) { + code = THUMB_PALETTE; + } + thumb = new JFIFThumbPalette(child); + } else if (name.equals("JFIFthumbRGB")) { + if (code == THUMB_UNASSIGNED) { + code = THUMB_RGB; + } + thumb = new JFIFThumbRGB(child); + } else { + throw new IIOInvalidTreeException + ("unrecognized app0JFXX child node", node); + } + } + + JFIFExtensionMarkerSegment(BufferedImage thumbnail) + throws IllegalThumbException { + + super(JPEG.APP0); + ColorModel cm = thumbnail.getColorModel(); + int csType = cm.getColorSpace().getType(); + if (cm.hasAlpha()) { + throw new IllegalThumbException(); + } + if (cm instanceof IndexColorModel) { + code = THUMB_PALETTE; + thumb = new JFIFThumbPalette(thumbnail); + } else if (csType == ColorSpace.TYPE_RGB) { + code = THUMB_RGB; + thumb = new JFIFThumbRGB(thumbnail); + } else if (csType == ColorSpace.TYPE_GRAY) { + code = THUMB_JPEG; + thumb = new JFIFThumbJPEG(thumbnail); + } else { + throw new IllegalThumbException(); + } + } + + void setThumbnail(BufferedImage thumbnail) { + try { + switch (code) { + case THUMB_PALETTE: + thumb = new JFIFThumbPalette(thumbnail); + break; + case THUMB_RGB: + thumb = new JFIFThumbRGB(thumbnail); + break; + case THUMB_JPEG: + thumb = new JFIFThumbJPEG(thumbnail); + break; + } + } catch (IllegalThumbException e) { + // Should never happen + throw new InternalError("Illegal thumb in setThumbnail!", e); + } + } + + protected Object clone() { + JFIFExtensionMarkerSegment newGuy = + (JFIFExtensionMarkerSegment) super.clone(); + if (thumb != null) { + newGuy.thumb = (JFIFThumb) thumb.clone(); + } + return newGuy; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("app0JFXX"); + node.setAttribute("extensionCode", Integer.toString(code)); + node.appendChild(thumb.getNativeNode()); + return node; + } + + void write(ImageOutputStream ios, + JPEGImageWriter writer) throws IOException { + length = LENGTH_SIZE + DATA_SIZE + thumb.getLength(); + writeTag(ios); + byte [] id = {0x4A, 0x46, 0x58, 0x58, 0x00}; + ios.write(id); + ios.write(code); + thumb.write(ios, writer); + } + + void print() { + printTag("JFXX"); + thumb.print(); + } + } + + /** + * A superclass for the varieties of thumbnails that can + * be stored in a JFIF extension marker segment. + */ + abstract class JFIFThumb implements Cloneable { + long streamPos = -1L; // Save the thumbnail pos when reading + abstract int getLength(); // When writing + abstract int getWidth(); + abstract int getHeight(); + abstract BufferedImage getThumbnail(ImageInputStream iis, + JPEGImageReader reader) + throws IOException; + + protected JFIFThumb() {} + + protected JFIFThumb(JPEGBuffer buffer) throws IOException{ + // Save the stream position for reading the thumbnail later + streamPos = buffer.getStreamPosition(); + } + + abstract void print(); + + abstract IIOMetadataNode getNativeNode(); + + abstract void write(ImageOutputStream ios, + JPEGImageWriter writer) throws IOException; + + protected Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) {} // won't happen + return null; + } + + } + + abstract class JFIFThumbUncompressed extends JFIFThumb { + BufferedImage thumbnail = null; + int thumbWidth; + int thumbHeight; + String name; + + JFIFThumbUncompressed(JPEGBuffer buffer, + int width, + int height, + int skip, + String name) + throws IOException { + super(buffer); + thumbWidth = width; + thumbHeight = height; + // Now skip the thumbnail data + buffer.skipData(skip); + this.name = name; + } + + JFIFThumbUncompressed(Node node, String name) + throws IIOInvalidTreeException { + + thumbWidth = 0; + thumbHeight = 0; + this.name = name; + NamedNodeMap attrs = node.getAttributes(); + int count = attrs.getLength(); + if (count > 2) { + throw new IIOInvalidTreeException + (name +" node cannot have > 2 attributes", node); + } + if (count != 0) { + int value = getAttributeValue(node, attrs, "thumbWidth", + 0, 255, false); + thumbWidth = (value != -1) ? value : thumbWidth; + value = getAttributeValue(node, attrs, "thumbHeight", + 0, 255, false); + thumbHeight = (value != -1) ? value : thumbHeight; + } + } + + JFIFThumbUncompressed(BufferedImage thumb) { + thumbnail = thumb; + thumbWidth = thumb.getWidth(); + thumbHeight = thumb.getHeight(); + name = null; // not used when writing + } + + void readByteBuffer(ImageInputStream iis, + byte [] data, + JPEGImageReader reader, + float workPortion, + float workOffset) throws IOException { + int progInterval = Math.max((int)(data.length/20/workPortion), + 1); + for (int offset = 0; + offset < data.length;) { + int len = Math.min(progInterval, data.length-offset); + iis.read(data, offset, len); + offset += progInterval; + float percentDone = ((float) offset* 100) + / data.length + * workPortion + workOffset; + if (percentDone > 100.0F) { + percentDone = 100.0F; + } + reader.thumbnailProgress (percentDone); + } + } + + + int getWidth() { + return thumbWidth; + } + + int getHeight() { + return thumbHeight; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode(name); + node.setAttribute("thumbWidth", Integer.toString(thumbWidth)); + node.setAttribute("thumbHeight", Integer.toString(thumbHeight)); + return node; + } + + void write(ImageOutputStream ios, + JPEGImageWriter writer) throws IOException { + if ((thumbWidth > MAX_THUMB_WIDTH) + || (thumbHeight > MAX_THUMB_HEIGHT)) { + writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED); + } + thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH); + thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT); + ios.write(thumbWidth); + ios.write(thumbHeight); + } + + void writePixels(ImageOutputStream ios, + JPEGImageWriter writer) throws IOException { + if ((thumbWidth > MAX_THUMB_WIDTH) + || (thumbHeight > MAX_THUMB_HEIGHT)) { + writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED); + } + thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH); + thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT); + int [] data = thumbnail.getRaster().getPixels(0, 0, + thumbWidth, + thumbHeight, + (int []) null); + writeThumbnailData(ios, data, writer); + } + + void print() { + System.out.print(name + " width: "); + System.out.println(thumbWidth); + System.out.print(name + " height: "); + System.out.println(thumbHeight); + } + + } + + /** + * A JFIF thumbnail stored as RGB, one byte per channel, + * interleaved. + */ + class JFIFThumbRGB extends JFIFThumbUncompressed { + + JFIFThumbRGB(JPEGBuffer buffer, int width, int height) + throws IOException { + + super(buffer, width, height, width*height*3, "JFIFthumbRGB"); + } + + JFIFThumbRGB(Node node) throws IIOInvalidTreeException { + super(node, "JFIFthumbRGB"); + } + + JFIFThumbRGB(BufferedImage thumb) throws IllegalThumbException { + super(thumb); + } + + int getLength() { + return (thumbWidth*thumbHeight*3); + } + + BufferedImage getThumbnail(ImageInputStream iis, + JPEGImageReader reader) + throws IOException { + iis.mark(); + iis.seek(streamPos); + DataBufferByte buffer = new DataBufferByte(getLength()); + readByteBuffer(iis, + buffer.getData(), + reader, + 1.0F, + 0.0F); + iis.reset(); + + WritableRaster raster = + Raster.createInterleavedRaster(buffer, + thumbWidth, + thumbHeight, + thumbWidth*3, + 3, + new int [] {0, 1, 2}, + null); + ColorModel cm = new ComponentColorModel(JPEG.JCS.sRGB, + false, + false, + ColorModel.OPAQUE, + DataBuffer.TYPE_BYTE); + return new BufferedImage(cm, + raster, + false, + null); + } + + void write(ImageOutputStream ios, + JPEGImageWriter writer) throws IOException { + super.write(ios, writer); // width and height + writePixels(ios, writer); + } + + } + + /** + * A JFIF thumbnail stored as an indexed palette image + * using an RGB palette. + */ + class JFIFThumbPalette extends JFIFThumbUncompressed { + private static final int PALETTE_SIZE = 768; + + JFIFThumbPalette(JPEGBuffer buffer, int width, int height) + throws IOException { + super(buffer, + width, + height, + PALETTE_SIZE + width * height, + "JFIFThumbPalette"); + } + + JFIFThumbPalette(Node node) throws IIOInvalidTreeException { + super(node, "JFIFThumbPalette"); + } + + JFIFThumbPalette(BufferedImage thumb) throws IllegalThumbException { + super(thumb); + IndexColorModel icm = (IndexColorModel) thumbnail.getColorModel(); + if (icm.getMapSize() > 256) { + throw new IllegalThumbException(); + } + } + + int getLength() { + return (thumbWidth*thumbHeight + PALETTE_SIZE); + } + + BufferedImage getThumbnail(ImageInputStream iis, + JPEGImageReader reader) + throws IOException { + iis.mark(); + iis.seek(streamPos); + // read the palette + byte [] palette = new byte [PALETTE_SIZE]; + float palettePart = ((float) PALETTE_SIZE) / getLength(); + readByteBuffer(iis, + palette, + reader, + palettePart, + 0.0F); + DataBufferByte buffer = new DataBufferByte(thumbWidth*thumbHeight); + readByteBuffer(iis, + buffer.getData(), + reader, + 1.0F-palettePart, + palettePart); + iis.read(); + iis.reset(); + + IndexColorModel cm = new IndexColorModel(8, + 256, + palette, + 0, + false); + SampleModel sm = cm.createCompatibleSampleModel(thumbWidth, + thumbHeight); + WritableRaster raster = + Raster.createWritableRaster(sm, buffer, null); + return new BufferedImage(cm, + raster, + false, + null); + } + + void write(ImageOutputStream ios, + JPEGImageWriter writer) throws IOException { + super.write(ios, writer); // width and height + // Write the palette (must be 768 bytes) + byte [] palette = new byte[768]; + IndexColorModel icm = (IndexColorModel) thumbnail.getColorModel(); + byte [] reds = new byte [256]; + byte [] greens = new byte [256]; + byte [] blues = new byte [256]; + icm.getReds(reds); + icm.getGreens(greens); + icm.getBlues(blues); + for (int i = 0; i < 256; i++) { + palette[i*3] = reds[i]; + palette[i*3+1] = greens[i]; + palette[i*3+2] = blues[i]; + } + ios.write(palette); + writePixels(ios, writer); + } + } + + + /** + * A JFIF thumbnail stored as a JPEG stream. No JFIF or + * JFIF extension markers are permitted. There is no need + * to clip these, but the entire image must fit into a + * single JFXX marker segment. + */ + class JFIFThumbJPEG extends JFIFThumb { + JPEGMetadata thumbMetadata = null; + byte [] data = null; // Compressed image data, for writing + private static final int PREAMBLE_SIZE = 6; + + JFIFThumbJPEG(JPEGBuffer buffer, + int length, + JPEGImageReader reader) throws IOException { + super(buffer); + // Compute the final stream position + long finalPos = streamPos + (length - PREAMBLE_SIZE); + // Set the stream back to the start of the thumbnail + // and read its metadata (but don't decode the image) + buffer.iis.seek(streamPos); + thumbMetadata = new JPEGMetadata(false, true, buffer.iis, reader); + // Set the stream to the computed final position + buffer.iis.seek(finalPos); + // Clear the now invalid buffer + buffer.bufAvail = 0; + buffer.bufPtr = 0; + } + + JFIFThumbJPEG(Node node) throws IIOInvalidTreeException { + if (node.getChildNodes().getLength() > 1) { + throw new IIOInvalidTreeException + ("JFIFThumbJPEG node must have 0 or 1 child", node); + } + Node child = node.getFirstChild(); + if (child != null) { + String name = child.getNodeName(); + if (!name.equals("markerSequence")) { + throw new IIOInvalidTreeException + ("JFIFThumbJPEG child must be a markerSequence node", + node); + } + thumbMetadata = new JPEGMetadata(false, true); + thumbMetadata.setFromMarkerSequenceNode(child); + } + } + + JFIFThumbJPEG(BufferedImage thumb) throws IllegalThumbException { + int INITIAL_BUFSIZE = 4096; + int MAZ_BUFSIZE = 65535 - 2 - PREAMBLE_SIZE; + try { + ByteArrayOutputStream baos = + new ByteArrayOutputStream(INITIAL_BUFSIZE); + MemoryCacheImageOutputStream mos = + new MemoryCacheImageOutputStream(baos); + + JPEGImageWriter thumbWriter = new JPEGImageWriter(null); + + thumbWriter.setOutput(mos); + + // get default metadata for the thumb + JPEGMetadata metadata = + (JPEGMetadata) thumbWriter.getDefaultImageMetadata + (new ImageTypeSpecifier(thumb), null); + + // Remove the jfif segment, which should be there. + MarkerSegment jfif = metadata.findMarkerSegment + (JFIFMarkerSegment.class, true); + if (jfif == null) { + throw new IllegalThumbException(); + } + + metadata.markerSequence.remove(jfif); + + /* Use this if removing leaves a hole and causes trouble + + // Get the tree + String format = metadata.getNativeMetadataFormatName(); + IIOMetadataNode tree = + (IIOMetadataNode) metadata.getAsTree(format); + + // If there is no app0jfif node, the image is bad + NodeList jfifs = tree.getElementsByTagName("app0JFIF"); + if (jfifs.getLength() == 0) { + throw new IllegalThumbException(); + } + + // remove the app0jfif node + Node jfif = jfifs.item(0); + Node parent = jfif.getParentNode(); + parent.removeChild(jfif); + + metadata.setFromTree(format, tree); + */ + + thumbWriter.write(new IIOImage(thumb, null, metadata)); + + thumbWriter.dispose(); + // Now check that the size is OK + if (baos.size() > MAZ_BUFSIZE) { + throw new IllegalThumbException(); + } + data = baos.toByteArray(); + } catch (IOException e) { + throw new IllegalThumbException(); + } + } + + int getWidth() { + int retval = 0; + SOFMarkerSegment sof = + (SOFMarkerSegment) thumbMetadata.findMarkerSegment + (SOFMarkerSegment.class, true); + if (sof != null) { + retval = sof.samplesPerLine; + } + return retval; + } + + int getHeight() { + int retval = 0; + SOFMarkerSegment sof = + (SOFMarkerSegment) thumbMetadata.findMarkerSegment + (SOFMarkerSegment.class, true); + if (sof != null) { + retval = sof.numLines; + } + return retval; + } + + private class ThumbnailReadListener + implements IIOReadProgressListener { + JPEGImageReader reader = null; + ThumbnailReadListener (JPEGImageReader reader) { + this.reader = reader; + } + public void sequenceStarted(ImageReader source, int minIndex) {} + public void sequenceComplete(ImageReader source) {} + public void imageStarted(ImageReader source, int imageIndex) {} + public void imageProgress(ImageReader source, + float percentageDone) { + reader.thumbnailProgress(percentageDone); + } + public void imageComplete(ImageReader source) {} + public void thumbnailStarted(ImageReader source, + int imageIndex, int thumbnailIndex) {} + public void thumbnailProgress(ImageReader source, float percentageDone) {} + public void thumbnailComplete(ImageReader source) {} + public void readAborted(ImageReader source) {} + } + + BufferedImage getThumbnail(ImageInputStream iis, + JPEGImageReader reader) + throws IOException { + iis.mark(); + iis.seek(streamPos); + JPEGImageReader thumbReader = new JPEGImageReader(null); + thumbReader.setInput(iis); + thumbReader.addIIOReadProgressListener + (new ThumbnailReadListener(reader)); + BufferedImage ret = thumbReader.read(0, null); + thumbReader.dispose(); + iis.reset(); + return ret; + } + + protected Object clone() { + JFIFThumbJPEG newGuy = (JFIFThumbJPEG) super.clone(); + if (thumbMetadata != null) { + newGuy.thumbMetadata = (JPEGMetadata) thumbMetadata.clone(); + } + return newGuy; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("JFIFthumbJPEG"); + if (thumbMetadata != null) { + node.appendChild(thumbMetadata.getNativeTree()); + } + return node; + } + + int getLength() { + if (data == null) { + return 0; + } else { + return data.length; + } + } + + void write(ImageOutputStream ios, + JPEGImageWriter writer) throws IOException { + int progInterval = data.length / 20; // approx. every 5% + if (progInterval == 0) { + progInterval = 1; + } + for (int offset = 0; + offset < data.length;) { + int len = Math.min(progInterval, data.length-offset); + ios.write(data, offset, len); + offset += progInterval; + float percentDone = ((float) offset * 100) / data.length; + if (percentDone > 100.0F) { + percentDone = 100.0F; + } + writer.thumbnailProgress (percentDone); + } + } + + void print () { + System.out.println("JFIF thumbnail stored as JPEG"); + } + } + + /** + * Write out the given profile to the stream, embedded in + * the necessary number of APP2 segments, per the ICC spec. + * This is the only mechanism for writing an ICC profile + * to a stream. + */ + static void writeICC(ICC_Profile profile, ImageOutputStream ios) + throws IOException { + int LENGTH_LENGTH = 2; + final String ID = "ICC_PROFILE"; + int ID_LENGTH = ID.length()+1; // spec says it's null-terminated + int COUNTS_LENGTH = 2; + int MAX_ICC_CHUNK_SIZE = + 65535 - LENGTH_LENGTH - ID_LENGTH - COUNTS_LENGTH; + + byte [] data = profile.getData(); + int numChunks = data.length / MAX_ICC_CHUNK_SIZE; + if ((data.length % MAX_ICC_CHUNK_SIZE) != 0) { + numChunks++; + } + int chunkNum = 1; + int offset = 0; + for (int i = 0; i < numChunks; i++) { + int dataLength = Math.min(data.length-offset, MAX_ICC_CHUNK_SIZE); + int segLength = dataLength+COUNTS_LENGTH+ID_LENGTH+LENGTH_LENGTH; + ios.write(0xff); + ios.write(JPEG.APP2); + MarkerSegment.write2bytes(ios, segLength); + byte [] id = ID.getBytes("US-ASCII"); + ios.write(id); + ios.write(0); // Null-terminate the string + ios.write(chunkNum++); + ios.write(numChunks); + ios.write(data, offset, dataLength); + offset += dataLength; + } + } + + /** + * An APP2 marker segment containing an ICC profile. In the stream + * a profile larger than 64K is broken up into a series of chunks. + * This inner class represents the complete profile as a single object, + * combining chunks as necessary. + */ + class ICCMarkerSegment extends MarkerSegment { + ArrayList chunks = null; + byte [] profile = null; // The complete profile when it's fully read + // May remain null when writing + private static final int ID_SIZE = 12; + int chunksRead; + int numChunks; + + ICCMarkerSegment(ICC_ColorSpace cs) { + super(JPEG.APP2); + chunks = null; + chunksRead = 0; + numChunks = 0; + profile = cs.getProfile().getData(); + } + + ICCMarkerSegment(JPEGBuffer buffer) throws IOException { + super(buffer); // gets whole segment or fills the buffer + if (debug) { + System.out.println("Creating new ICC segment"); + } + buffer.bufPtr += ID_SIZE; // Skip the id + buffer.bufAvail -= ID_SIZE; + /* + * Reduce the stored length by the id size. The stored + * length is used to store the length of the profile + * data only. + */ + length -= ID_SIZE; + + // get the chunk number + int chunkNum = buffer.buf[buffer.bufPtr] & 0xff; + // get the total number of chunks + numChunks = buffer.buf[buffer.bufPtr+1] & 0xff; + + if (chunkNum > numChunks) { + throw new IIOException + ("Image format Error; chunk num > num chunks"); + } + + // if there are no more chunks, set up the data + if (numChunks == 1) { + // reduce the stored length by the two chunk numbering bytes + length -= 2; + profile = new byte[length]; + buffer.bufPtr += 2; + buffer.bufAvail-=2; + buffer.readData(profile); + inICC = false; + } else { + // If we store them away, include the chunk numbering bytes + byte [] profileData = new byte[length]; + // Now reduce the stored length by the + // two chunk numbering bytes + length -= 2; + buffer.readData(profileData); + chunks = new ArrayList(); + chunks.add(profileData); + chunksRead = 1; + inICC = true; + } + } + + ICCMarkerSegment(Node node) throws IIOInvalidTreeException { + super(JPEG.APP2); + if (node instanceof IIOMetadataNode) { + IIOMetadataNode ourNode = (IIOMetadataNode) node; + ICC_Profile prof = (ICC_Profile) ourNode.getUserObject(); + if (prof != null) { // May be null + profile = prof.getData(); + } + } + } + + protected Object clone () { + ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone(); + if (profile != null) { + newGuy.profile = (byte[]) profile.clone(); + } + return newGuy; + } + + boolean addData(JPEGBuffer buffer) throws IOException { + if (debug) { + System.out.println("Adding to ICC segment"); + } + // skip the tag + buffer.bufPtr++; + buffer.bufAvail--; + // Get the length, but not in length + int dataLen = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; + dataLen |= buffer.buf[buffer.bufPtr++] & 0xff; + buffer.bufAvail -= 2; + // Don't include length itself + dataLen -= 2; + // skip the id + buffer.bufPtr += ID_SIZE; // Skip the id + buffer.bufAvail -= ID_SIZE; + /* + * Reduce the stored length by the id size. The stored + * length is used to store the length of the profile + * data only. + */ + dataLen -= ID_SIZE; + + // get the chunk number + int chunkNum = buffer.buf[buffer.bufPtr] & 0xff; + if (chunkNum > numChunks) { + throw new IIOException + ("Image format Error; chunk num > num chunks"); + } + + // get the number of chunks, which should match + int newNumChunks = buffer.buf[buffer.bufPtr+1] & 0xff; + if (numChunks != newNumChunks) { + throw new IIOException + ("Image format Error; icc num chunks mismatch"); + } + dataLen -= 2; + if (debug) { + System.out.println("chunkNum: " + chunkNum + + ", numChunks: " + numChunks + + ", dataLen: " + dataLen); + } + boolean retval = false; + byte [] profileData = new byte[dataLen]; + buffer.readData(profileData); + chunks.add(profileData); + length += dataLen; + chunksRead++; + if (chunksRead < numChunks) { + inICC = true; + } else { + if (debug) { + System.out.println("Completing profile; total length is " + + length); + } + // create an array for the whole thing + profile = new byte[length]; + // copy the existing chunks, releasing them + // Note that they may be out of order + + int index = 0; + for (int i = 1; i <= numChunks; i++) { + boolean foundIt = false; + for (int chunk = 0; chunk < chunks.size(); chunk++) { + byte [] chunkData = (byte []) chunks.get(chunk); + if (chunkData[0] == i) { // Right one + System.arraycopy(chunkData, 2, + profile, index, + chunkData.length-2); + index += chunkData.length-2; + foundIt = true; + } + } + if (foundIt == false) { + throw new IIOException + ("Image Format Error: Missing ICC chunk num " + i); + } + } + + chunks = null; + chunksRead = 0; + numChunks = 0; + inICC = false; + retval = true; + } + return retval; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("app2ICC"); + if (profile != null) { + node.setUserObject(ICC_Profile.getInstance(profile)); + } + return node; + } + + /** + * No-op. Profiles are never written from metadata. + * They are written from the ColorSpace of the image. + */ + void write(ImageOutputStream ios) throws IOException { + // No-op + } + + void print () { + printTag("ICC Profile APP2"); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEG.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEG.java new file mode 100644 index 000000000..e18e9509e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEG.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.plugins.jpeg.JPEGQTable; +import javax.imageio.plugins.jpeg.JPEGHuffmanTable; + +import java.awt.image.ColorModel; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; + +/** + * A class containing JPEG-related constants, definitions, and + * static methods. This class and its constants must be public so that + * JPEGImageWriteParam can see it. + */ +public class JPEG { + + // List of all the JPEG markers (pre-JPEG2000) + + /** For temporary use in arithmetic coding */ + public static final int TEM = 0x01; + + // Codes 0x02 - 0xBF are reserved + + // SOF markers for Nondifferential Huffman coding + /** Baseline DCT */ + public static final int SOF0 = 0xC0; + /** Extended Sequential DCT */ + public static final int SOF1 = 0xC1; + /** Progressive DCT */ + public static final int SOF2 = 0xC2; + /** Lossless Sequential */ + public static final int SOF3 = 0xC3; + + /** Define Huffman Tables */ + public static final int DHT = 0xC4; + + // SOF markers for Differential Huffman coding + /** Differential Sequential DCT */ + public static final int SOF5 = 0xC5; + /** Differential Progressive DCT */ + public static final int SOF6 = 0xC6; + /** Differential Lossless */ + public static final int SOF7 = 0xC7; + + /** Reserved for JPEG extensions */ + public static final int JPG = 0xC8; + + // SOF markers for Nondifferential arithmetic coding + /** Extended Sequential DCT, Arithmetic coding */ + public static final int SOF9 = 0xC9; + /** Progressive DCT, Arithmetic coding */ + public static final int SOF10 = 0xCA; + /** Lossless Sequential, Arithmetic coding */ + public static final int SOF11 = 0xCB; + + /** Define Arithmetic conditioning tables */ + public static final int DAC = 0xCC; + + // SOF markers for Differential arithmetic coding + /** Differential Sequential DCT, Arithmetic coding */ + public static final int SOF13 = 0xCD; + /** Differential Progressive DCT, Arithmetic coding */ + public static final int SOF14 = 0xCE; + /** Differential Lossless, Arithmetic coding */ + public static final int SOF15 = 0xCF; + + // Restart Markers + public static final int RST0 = 0xD0; + public static final int RST1 = 0xD1; + public static final int RST2 = 0xD2; + public static final int RST3 = 0xD3; + public static final int RST4 = 0xD4; + public static final int RST5 = 0xD5; + public static final int RST6 = 0xD6; + public static final int RST7 = 0xD7; + /** Number of restart markers */ + public static final int RESTART_RANGE = 8; + + /** Start of Image */ + public static final int SOI = 0xD8; + /** End of Image */ + public static final int EOI = 0xD9; + /** Start of Scan */ + public static final int SOS = 0xDA; + + /** Define Quantisation Tables */ + public static final int DQT = 0xDB; + + /** Define Number of lines */ + public static final int DNL = 0xDC; + + /** Define Restart Interval */ + public static final int DRI = 0xDD; + + /** Define Heirarchical progression */ + public static final int DHP = 0xDE; + + /** Expand reference image(s) */ + public static final int EXP = 0xDF; + + // Application markers + /** APP0 used by JFIF */ + public static final int APP0 = 0xE0; + public static final int APP1 = 0xE1; + public static final int APP2 = 0xE2; + public static final int APP3 = 0xE3; + public static final int APP4 = 0xE4; + public static final int APP5 = 0xE5; + public static final int APP6 = 0xE6; + public static final int APP7 = 0xE7; + public static final int APP8 = 0xE8; + public static final int APP9 = 0xE9; + public static final int APP10 = 0xEA; + public static final int APP11 = 0xEB; + public static final int APP12 = 0xEC; + public static final int APP13 = 0xED; + /** APP14 used by Adobe */ + public static final int APP14 = 0xEE; + public static final int APP15 = 0xEF; + + // codes 0xF0 to 0xFD are reserved + + /** Comment marker */ + public static final int COM = 0xFE; + + // JFIF Resolution units + /** The X and Y units simply indicate the aspect ratio of the pixels. */ + public static final int DENSITY_UNIT_ASPECT_RATIO = 0; + /** Pixel density is in pixels per inch. */ + public static final int DENSITY_UNIT_DOTS_INCH = 1; + /** Pixel density is in pixels per centemeter. */ + public static final int DENSITY_UNIT_DOTS_CM = 2; + /** The max known value for DENSITY_UNIT */ + public static final int NUM_DENSITY_UNIT = 3; + + // Adobe transform values + public static final int ADOBE_IMPOSSIBLE = -1; + public static final int ADOBE_UNKNOWN = 0; + public static final int ADOBE_YCC = 1; + public static final int ADOBE_YCCK = 2; + + // Spi initialization stuff + public static final String vendor = "Oracle Corporation"; + public static final String version = "0.5"; + // Names of the formats we can read or write + static final String [] names = {"JPEG", "jpeg", "JPG", "jpg"}; + static final String [] suffixes = {"jpg", "jpeg"}; + static final String [] MIMETypes = {"image/jpeg"}; + public static final String nativeImageMetadataFormatName = + "javax_imageio_jpeg_image_1.0"; + public static final String nativeImageMetadataFormatClassName = + "com.sun.imageio.plugins.jpeg.JPEGImageMetadataFormat"; + public static final String nativeStreamMetadataFormatName = + "javax_imageio_jpeg_stream_1.0"; + public static final String nativeStreamMetadataFormatClassName = + "com.sun.imageio.plugins.jpeg.JPEGStreamMetadataFormat"; + + // IJG Color codes. + public static final int JCS_UNKNOWN = 0; // error/unspecified + public static final int JCS_GRAYSCALE = 1; // monochrome + public static final int JCS_RGB = 2; // red/green/blue + public static final int JCS_YCbCr = 3; // Y/Cb/Cr (also known as YUV) + public static final int JCS_CMYK = 4; // C/M/Y/K + public static final int JCS_YCC = 5; // PhotoYCC + public static final int JCS_RGBA = 6; // RGB-Alpha + public static final int JCS_YCbCrA = 7; // Y/Cb/Cr/Alpha + // 8 and 9 were old "Legacy" codes which the old code never identified + // on reading anyway. Support for writing them is being dropped, too. + public static final int JCS_YCCA = 10; // PhotoYCC-Alpha + public static final int JCS_YCCK = 11; // Y/Cb/Cr/K + + public static final int NUM_JCS_CODES = JCS_YCCK+1; + + /** IJG can handle up to 4-channel JPEGs */ + static final int [] [] bandOffsets = {{0}, + {0, 1}, + {0, 1, 2}, + {0, 1, 2, 3}}; + + static final int [] bOffsRGB = { 2, 1, 0 }; + + /* These are kept in the inner class to avoid static initialization + * of the CMM class until someone actually needs it. + * (e.g. do not init CMM on the request for jpeg mime types) + */ + public static class JCS { + public static final ColorSpace sRGB = + ColorSpace.getInstance(ColorSpace.CS_sRGB); + + private static ColorSpace YCC = null; + private static boolean yccInited = false; + + public static ColorSpace getYCC() { + if (!yccInited) { + try { + YCC = ColorSpace.getInstance(ColorSpace.CS_PYCC); + } catch (IllegalArgumentException e) { + // PYCC.pf may not always be installed + } finally { + yccInited = true; + } + } + return YCC; + } + } + + // Default value for ImageWriteParam + public static final float DEFAULT_QUALITY = 0.75F; + + /** + * Returns true if the given ColorSpace + * object is an instance of ICC_ColorSpace but is not one of the + * standard ColorSpaces returned by + * ColorSpace.getInstance(). + */ + static boolean isNonStandardICC(ColorSpace cs) { + boolean retval = false; + if ((cs instanceof ICC_ColorSpace) + && (!cs.isCS_sRGB()) + && (!cs.equals(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ))) + && (!cs.equals(ColorSpace.getInstance(ColorSpace.CS_GRAY))) + && (!cs.equals(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB))) + && (!cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))) + ) { + retval = true; + } + return retval; + } + + + /** + * Returns true if the given imageType can be used + * in a JFIF file. If input is true, then the + * image type is considered before colorspace conversion. + */ + static boolean isJFIFcompliant(ImageTypeSpecifier imageType, + boolean input) { + ColorModel cm = imageType.getColorModel(); + // Can't have alpha + if (cm.hasAlpha()) { + return false; + } + // Gray is OK, always + int numComponents = imageType.getNumComponents(); + if (numComponents == 1) { + return true; + } + + // If it isn't gray, it must have 3 channels + if (numComponents != 3) { + return false; + } + + if (input) { + // Must be RGB + if (cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) { + return true; + } + } else { + // Must be YCbCr + if (cm.getColorSpace().getType() == ColorSpace.TYPE_YCbCr) { + return true; + } + } + + return false; + } + + /** + * Given an image type, return the Adobe transform corresponding to + * that type, or ADOBE_IMPOSSIBLE if the image type is incompatible + * with an Adobe marker segment. If input is true, then + * the image type is considered before colorspace conversion. + */ + static int transformForType(ImageTypeSpecifier imageType, boolean input) { + int retval = ADOBE_IMPOSSIBLE; + ColorModel cm = imageType.getColorModel(); + switch (cm.getColorSpace().getType()) { + case ColorSpace.TYPE_GRAY: + retval = ADOBE_UNKNOWN; + break; + case ColorSpace.TYPE_RGB: + retval = input ? ADOBE_YCC : ADOBE_UNKNOWN; + break; + case ColorSpace.TYPE_YCbCr: + retval = ADOBE_YCC; + break; + case ColorSpace.TYPE_CMYK: + retval = input ? ADOBE_YCCK : ADOBE_IMPOSSIBLE; + } + return retval; + } + + /** + * Converts an ImageWriteParam (i.e. IJG) non-linear quality value + * to a float suitable for passing to JPEGQTable.getScaledInstance(). + */ + static float convertToLinearQuality(float quality) { + // The following is converted from the IJG code. + if (quality <= 0.0F) { + quality = 0.01F; + } + + if (quality > 1.00F) { + quality = 1.00F; + } + + if (quality < 0.5F) { + quality = 0.5F / quality; + } else { + quality = 2.0F - (quality * 2.0F); + } + + return quality; + } + + /** + * Return an array of default, visually lossless quantization tables. + */ + static JPEGQTable [] getDefaultQTables() { + JPEGQTable [] qTables = new JPEGQTable[2]; + qTables[0] = JPEGQTable.K1Div2Luminance; + qTables[1] = JPEGQTable.K2Div2Chrominance; + return qTables; + } + + /** + * Return an array of default Huffman tables. + */ + static JPEGHuffmanTable [] getDefaultHuffmanTables(boolean wantDC) { + JPEGHuffmanTable [] tables = new JPEGHuffmanTable[2]; + if (wantDC) { + tables[0] = JPEGHuffmanTable.StdDCLuminance; + tables[1] = JPEGHuffmanTable.StdDCChrominance; + } else { + tables[0] = JPEGHuffmanTable.StdACLuminance; + tables[1] = JPEGHuffmanTable.StdACChrominance; + } + return tables; + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGBuffer.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGBuffer.java new file mode 100644 index 000000000..60bdbf087 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGBuffer.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.stream.ImageInputStream; +import javax.imageio.IIOException; + +import java.io.IOException; + +/** + * A class wrapping a buffer and its state. For efficiency, + * the members are made visible to other classes in this package. + */ +class JPEGBuffer { + + private boolean debug = false; + + /** + * The size of the buffer. This is large enough to hold all + * known marker segments (other than thumbnails and icc profiles) + */ + final int BUFFER_SIZE = 4096; + + /** + * The actual buffer. + */ + byte [] buf; + + /** + * The number of bytes available for reading from the buffer. + * Anytime data is read from the buffer, this should be updated. + */ + int bufAvail; + + /** + * A pointer to the next available byte in the buffer. This is + * used to read data from the buffer and must be updated to + * move through the buffer. + */ + int bufPtr; + + /** + * The ImageInputStream buffered. + */ + ImageInputStream iis; + + JPEGBuffer (ImageInputStream iis) { + buf = new byte[BUFFER_SIZE]; + bufAvail = 0; + bufPtr = 0; + this.iis = iis; + } + + /** + * Ensures that there are at least count bytes available + * in the buffer, loading more data and moving any remaining + * bytes to the front. A count of 0 means to just fill the buffer. + * If the count is larger than the buffer size, just fills the buffer. + * If the end of the stream is encountered before a non-0 count can + * be satisfied, an IIOException is thrown with the + * message "Image Format Error". + */ + void loadBuf(int count) throws IOException { + if (debug) { + System.out.print("loadbuf called with "); + System.out.print("count " + count + ", "); + System.out.println("bufAvail " + bufAvail + ", "); + } + if (count != 0) { + if (bufAvail >= count) { // have enough + return; + } + } else { + if (bufAvail == BUFFER_SIZE) { // already full + return; + } + } + // First copy any remaining bytes down to the beginning + if ((bufAvail > 0) && (bufAvail < BUFFER_SIZE)) { + System.arraycopy(buf, bufPtr, buf, 0, bufAvail); + } + // Now fill the rest of the buffer + int ret = iis.read(buf, bufAvail, buf.length - bufAvail); + if (debug) { + System.out.println("iis.read returned " + ret); + } + if (ret != -1) { + bufAvail += ret; + } + bufPtr = 0; + int minimum = Math.min(BUFFER_SIZE, count); + if (bufAvail < minimum) { + throw new IIOException ("Image Format Error"); + } + } + + /** + * Fills the data array from the stream, starting with + * the buffer and then reading directly from the stream + * if necessary. The buffer is left in an appropriate + * state. If the end of the stream is encountered, an + * IIOException is thrown with the + * message "Image Format Error". + */ + void readData(byte [] data) throws IOException { + int count = data.length; + // First see what's left in the buffer. + if (bufAvail >= count) { // It's enough + System.arraycopy(buf, bufPtr, data, 0, count); + bufAvail -= count; + bufPtr += count; + return; + } + int offset = 0; + if (bufAvail > 0) { // Some there, but not enough + System.arraycopy(buf, bufPtr, data, 0, bufAvail); + offset = bufAvail; + count -= bufAvail; + bufAvail = 0; + bufPtr = 0; + } + // Now read the rest directly from the stream + if (iis.read(data, offset, count) != count) { + throw new IIOException ("Image format Error"); + } + } + + /** + * Skips count bytes, leaving the buffer + * in an appropriate state. If the end of the stream is + * encountered, an IIOException is thrown with the + * message "Image Format Error". + */ + void skipData(int count) throws IOException { + // First see what's left in the buffer. + if (bufAvail >= count) { // It's enough + bufAvail -= count; + bufPtr += count; + return; + } + if (bufAvail > 0) { // Some there, but not enough + count -= bufAvail; + bufAvail = 0; + bufPtr = 0; + } + // Now read the rest directly from the stream + if (iis.skipBytes(count) != count) { + throw new IIOException ("Image format Error"); + } + } + + /** + * Push back the remaining contents of the buffer by + * repositioning the input stream. + */ + void pushBack() throws IOException { + iis.seek(iis.getStreamPosition()-bufAvail); + bufAvail = 0; + bufPtr = 0; + } + + /** + * Return the stream position corresponding to the next + * available byte in the buffer. + */ + long getStreamPosition() throws IOException { + return (iis.getStreamPosition()-bufAvail); + } + + /** + * Scan the buffer until the next 0xff byte, reloading + * the buffer as necessary. The buffer position is left + * pointing to the first non-0xff byte after a run of + * 0xff bytes. If the end of the stream is encountered, + * an EOI marker is inserted into the buffer and true + * is returned. Otherwise returns false. + */ + boolean scanForFF(JPEGImageReader reader) throws IOException { + boolean retval = false; + boolean foundFF = false; + while (foundFF == false) { + while (bufAvail > 0) { + if ((buf[bufPtr++] & 0xff) == 0xff) { + bufAvail--; + foundFF = true; + break; // out of inner while + } + bufAvail--; + } + // Reload the buffer and keep going + loadBuf(0); + // Skip any remaining pad bytes + if (foundFF == true) { + while ((bufAvail > 0) && (buf[bufPtr] & 0xff) == 0xff) { + bufPtr++; // Only if it still is 0xff + bufAvail--; + } + } + if (bufAvail == 0) { // Premature EOF + // send out a warning, but treat it as EOI + //reader.warningOccurred(JPEGImageReader.WARNING_NO_EOI); + retval = true; + buf[0] = (byte)JPEG.EOI; + bufAvail = 1; + bufPtr = 0; + foundFF = true; + } + } + return retval; + } + + /** + * Prints the contents of the buffer, in hex. + * @param count the number of bytes to print, + * starting at the current available byte. + */ + void print(int count) { + System.out.print("buffer has "); + System.out.print(bufAvail); + System.out.println(" bytes available"); + if (bufAvail < count) { + count = bufAvail; + } + for (int ptr = bufPtr; count > 0; count--) { + int val = (int)buf[ptr++] & 0xff; + System.out.print(" " + Integer.toHexString(val)); + } + System.out.println(); + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormat.java new file mode 100644 index 000000000..92c3e5963 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormat.java @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.ImageTypeSpecifier; + +import java.awt.color.ICC_Profile; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; + +import java.util.List; +import java.util.ArrayList; + +public class JPEGImageMetadataFormat extends JPEGMetadataFormat { + + private static JPEGImageMetadataFormat theInstance = null; + + private JPEGImageMetadataFormat() { + super(JPEG.nativeImageMetadataFormatName, + CHILD_POLICY_ALL); + + addElement("JPEGvariety", + JPEG.nativeImageMetadataFormatName, + CHILD_POLICY_CHOICE); + + addElement("markerSequence", + JPEG.nativeImageMetadataFormatName, + CHILD_POLICY_SEQUENCE); + + addElement("app0JFIF", "JPEGvariety", CHILD_POLICY_SOME); + + addStreamElements("markerSequence"); + + addElement("app14Adobe", "markerSequence", CHILD_POLICY_EMPTY); + + addElement("sof", "markerSequence", 1, 4); + + addElement("sos", "markerSequence", 1, 4); + + addElement("JFXX", "app0JFIF", 1, Integer.MAX_VALUE); + + addElement("app0JFXX", "JFXX", CHILD_POLICY_CHOICE); + + addElement("app2ICC", "app0JFIF", CHILD_POLICY_EMPTY); + + addAttribute("app0JFIF", + "majorVersion", + DATATYPE_INTEGER, + false, + "1", + "0", "255", + true, true); + addAttribute("app0JFIF", + "minorVersion", + DATATYPE_INTEGER, + false, + "2", + "0", "255", + true, true); + List resUnits = new ArrayList(); + resUnits.add("0"); + resUnits.add("1"); + resUnits.add("2"); + addAttribute("app0JFIF", + "resUnits", + DATATYPE_INTEGER, + false, + "0", + resUnits); + addAttribute("app0JFIF", + "Xdensity", + DATATYPE_INTEGER, + false, + "1", + "1", "65535", + true, true); + addAttribute("app0JFIF", + "Ydensity", + DATATYPE_INTEGER, + false, + "1", + "1", "65535", + true, true); + addAttribute("app0JFIF", + "thumbWidth", + DATATYPE_INTEGER, + false, + "0", + "0", "255", + true, true); + addAttribute("app0JFIF", + "thumbHeight", + DATATYPE_INTEGER, + false, + "0", + "0", "255", + true, true); + + addElement("JFIFthumbJPEG", "app0JFXX", CHILD_POLICY_SOME); + addElement("JFIFthumbPalette", "app0JFXX", CHILD_POLICY_EMPTY); + addElement("JFIFthumbRGB", "app0JFXX", CHILD_POLICY_EMPTY); + + List codes = new ArrayList(); + codes.add("16"); // Hex 10 + codes.add("17"); // Hex 11 + codes.add("19"); // Hex 13 + addAttribute("app0JFXX", + "extensionCode", + DATATYPE_INTEGER, + false, + null, + codes); + + addChildElement("markerSequence", "JFIFthumbJPEG"); + + addAttribute("JFIFthumbPalette", + "thumbWidth", + DATATYPE_INTEGER, + false, + null, + "0", "255", + true, true); + addAttribute("JFIFthumbPalette", + "thumbHeight", + DATATYPE_INTEGER, + false, + null, + "0", "255", + true, true); + + addAttribute("JFIFthumbRGB", + "thumbWidth", + DATATYPE_INTEGER, + false, + null, + "0", "255", + true, true); + addAttribute("JFIFthumbRGB", + "thumbHeight", + DATATYPE_INTEGER, + false, + null, + "0", "255", + true, true); + + addObjectValue("app2ICC", ICC_Profile.class, false, null); + + addAttribute("app14Adobe", + "version", + DATATYPE_INTEGER, + false, + "100", + "100", "255", + true, true); + addAttribute("app14Adobe", + "flags0", + DATATYPE_INTEGER, + false, + "0", + "0", "65535", + true, true); + addAttribute("app14Adobe", + "flags1", + DATATYPE_INTEGER, + false, + "0", + "0", "65535", + true, true); + + List transforms = new ArrayList(); + transforms.add("0"); + transforms.add("1"); + transforms.add("2"); + addAttribute("app14Adobe", + "transform", + DATATYPE_INTEGER, + true, + null, + transforms); + + addElement("componentSpec", "sof", CHILD_POLICY_EMPTY); + + List procs = new ArrayList(); + procs.add("0"); + procs.add("1"); + procs.add("2"); + addAttribute("sof", + "process", + DATATYPE_INTEGER, + false, + null, + procs); + addAttribute("sof", + "samplePrecision", + DATATYPE_INTEGER, + false, + "8"); + addAttribute("sof", + "numLines", + DATATYPE_INTEGER, + false, + null, + "0", "65535", + true, true); + addAttribute("sof", + "samplesPerLine", + DATATYPE_INTEGER, + false, + null, + "0", "65535", + true, true); + List comps = new ArrayList(); + comps.add("1"); + comps.add("2"); + comps.add("3"); + comps.add("4"); + addAttribute("sof", + "numFrameComponents", + DATATYPE_INTEGER, + false, + null, + comps); + + addAttribute("componentSpec", + "componentId", + DATATYPE_INTEGER, + true, + null, + "0", "255", + true, true); + addAttribute("componentSpec", + "HsamplingFactor", + DATATYPE_INTEGER, + true, + null, + "1", "255", + true, true); + addAttribute("componentSpec", + "VsamplingFactor", + DATATYPE_INTEGER, + true, + null, + "1", "255", + true, true); + List tabids = new ArrayList(); + tabids.add("0"); + tabids.add("1"); + tabids.add("2"); + tabids.add("3"); + addAttribute("componentSpec", + "QtableSelector", + DATATYPE_INTEGER, + true, + null, + tabids); + + addElement("scanComponentSpec", "sos", CHILD_POLICY_EMPTY); + + addAttribute("sos", + "numScanComponents", + DATATYPE_INTEGER, + true, + null, + comps); + addAttribute("sos", + "startSpectralSelection", + DATATYPE_INTEGER, + false, + "0", + "0", "63", + true, true); + addAttribute("sos", + "endSpectralSelection", + DATATYPE_INTEGER, + false, + "63", + "0", "63", + true, true); + addAttribute("sos", + "approxHigh", + DATATYPE_INTEGER, + false, + "0", + "0", "15", + true, true); + addAttribute("sos", + "approxLow", + DATATYPE_INTEGER, + false, + "0", + "0", "15", + true, true); + + addAttribute("scanComponentSpec", + "componentSelector", + DATATYPE_INTEGER, + true, + null, + "0", "255", + true, true); + addAttribute("scanComponentSpec", + "dcHuffTable", + DATATYPE_INTEGER, + true, + null, + tabids); + addAttribute("scanComponentSpec", + "acHuffTable", + DATATYPE_INTEGER, + true, + null, + tabids); + } + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + // All images can have these + if (elementName.equals(getRootName()) + || elementName.equals("JPEGvariety") + || isInSubtree(elementName, "markerSequence")) { + return true; + } + + // If it is an element in the app0jfif subtree, just check + // that the image type is JFIF compliant. + if ((isInSubtree(elementName, "app0JFIF")) + && JPEG.isJFIFcompliant(imageType, true)) { + return true; + } + + return false; + } + + + public static synchronized IIOMetadataFormat getInstance() { + if (theInstance == null) { + theInstance = new JPEGImageMetadataFormat(); + } + return theInstance; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormatResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormatResources.java new file mode 100644 index 000000000..e242a355a --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageMetadataFormatResources.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import java.util.ListResourceBundle; + +public class JPEGImageMetadataFormatResources + extends JPEGMetadataFormatResources { + + static final Object[][] imageContents = { + // Node name, followed by description + { "JPEGvariety", "A node grouping all marker segments specific to the variety of stream being read/written (e.g. JFIF) - may be empty" }, + { "markerSequence", "A node grouping all non-jfif marker segments" }, + { "app0jfif", "A JFIF APP0 marker segment" }, + { "app14Adobe", "An Adobe APP14 marker segment" }, + { "sof", "A Start Of Frame marker segment" }, + { "sos", "A Start Of Scan marker segment" }, + { "app0JFXX", "A JFIF extension marker segment" }, + { "app2ICC", "An ICC profile APP2 marker segment" }, + { "JFIFthumbJPEG", + "A JFIF thumbnail in JPEG format (no JFIF segments permitted)" }, + { "JFIFthumbPalette", "A JFIF thumbnail as an RGB indexed image" }, + { "JFIFthumbRGB", "A JFIF thumbnail as an RGB image" }, + { "componentSpec", "A component specification for a frame" }, + { "scanComponentSpec", "A component specification for a scan" }, + + // Node name + "/" + AttributeName, followed by description + { "app0JFIF/majorVersion", + "The major JFIF version number" }, + { "app0JFIF/minorVersion", + "The minor JFIF version number" }, + { "app0JFIF/resUnits", + "The resolution units for Xdensity and Ydensity " + + "(0 = no units, just aspect ratio; 1 = dots/inch; 2 = dots/cm)" }, + { "app0JFIF/Xdensity", + "The horizontal density or aspect ratio numerator" }, + { "app0JFIF/Ydensity", + "The vertical density or aspect ratio denominator" }, + { "app0JFIF/thumbWidth", + "The width of the thumbnail, or 0 if there isn't one" }, + { "app0JFIF/thumbHeight", + "The height of the thumbnail, or 0 if there isn't one" }, + { "app0JFXX/extensionCode", + "The JFXX extension code identifying thumbnail type: " + + "(16 = JPEG, 17 = indexed, 19 = RGB" }, + { "JFIFthumbPalette/thumbWidth", + "The width of the thumbnail" }, + { "JFIFthumbPalette/thumbHeight", + "The height of the thumbnail" }, + { "JFIFthumbRGB/thumbWidth", + "The width of the thumbnail" }, + { "JFIFthumbRGB/thumbHeight", + "The height of the thumbnail" }, + { "app14Adobe/version", + "The version of Adobe APP14 marker segment" }, + { "app14Adobe/flags0", + "The flags0 variable of an APP14 marker segment" }, + { "app14Adobe/flags1", + "The flags1 variable of an APP14 marker segment" }, + { "app14Adobe/transform", + "The color transform applied to the image " + + "(0 = Unknown, 1 = YCbCr, 2 = YCCK)" }, + { "sof/process", + "The JPEG process (0 = Baseline sequential, " + + "1 = Extended sequential, 2 = Progressive)" }, + { "sof/samplePrecision", + "The number of bits per sample" }, + { "sof/numLines", + "The number of lines in the image" }, + { "sof/samplesPerLine", + "The number of samples per line" }, + { "sof/numFrameComponents", + "The number of components in the image" }, + { "componentSpec/componentId", + "The id for this component" }, + { "componentSpec/HsamplingFactor", + "The horizontal sampling factor for this component" }, + { "componentSpec/VsamplingFactor", + "The vertical sampling factor for this component" }, + { "componentSpec/QtableSelector", + "The quantization table to use for this component" }, + { "sos/numScanComponents", + "The number of components in the scan" }, + { "sos/startSpectralSelection", + "The first spectral band included in this scan" }, + { "sos/endSpectralSelection", + "The last spectral band included in this scan" }, + { "sos/approxHigh", + "The highest bit position included in this scan" }, + { "sos/approxLow", + "The lowest bit position included in this scan" }, + { "scanComponentSpec/componentSelector", + "The id of this component" }, + { "scanComponentSpec/dcHuffTable", + "The huffman table to use for encoding DC coefficients" }, + { "scanComponentSpec/acHuffTable", + "The huffman table to use for encoding AC coefficients" } + }; + + public JPEGImageMetadataFormatResources() {} + + protected Object[][] getContents() { + // return a copy of the combined commonContents and imageContents; + // in theory we want a deep clone of the combined arrays, + // but since it only contains (immutable) Strings, this shallow + // copy is sufficient + Object[][] combinedContents = + new Object[commonContents.length + imageContents.length][2]; + int combined = 0; + for (int i = 0; i < commonContents.length; i++, combined++) { + combinedContents[combined][0] = commonContents[i][0]; + combinedContents[combined][1] = commonContents[i][1]; + } + for (int i = 0; i < imageContents.length; i++, combined++) { + combinedContents[combined][0] = imageContents[i][0]; + combinedContents[combined][1] = imageContents[i][1]; + } + return combinedContents; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReader.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReader.java new file mode 100644 index 000000000..c3db8e18b --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReader.java @@ -0,0 +1,1830 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.IIOException; +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.plugins.jpeg.JPEGImageReadParam; +import javax.imageio.plugins.jpeg.JPEGQTable; +import javax.imageio.plugins.jpeg.JPEGHuffmanTable; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.CMMException; +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.ColorConvertOp; +import java.io.IOException; +import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.NoSuchElementException; + +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; + +public class JPEGImageReader extends ImageReader { + + private boolean debug = false; + + /** + * The following variable contains a pointer to the IJG library + * structure for this reader. It is assigned in the constructor + * and then is passed in to every native call. It is set to 0 + * by dispose to avoid disposing twice. + */ + private long structPointer = 0; + + /** The input stream we read from */ + private ImageInputStream iis = null; + + /** + * List of stream positions for images, reinitialized every time + * a new input source is set. + */ + private List imagePositions = null; + + /** + * The number of images in the stream, or 0. + */ + private int numImages = 0; + + static { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Void run() { + System.loadLibrary("jpeg"); + return null; + } + }); + initReaderIDs(ImageInputStream.class, + JPEGQTable.class, + JPEGHuffmanTable.class); + } + + // The following warnings are converted to strings when used + // as keys to get localized resources from JPEGImageReaderResources + // and its children. + + /** + * Warning code to be passed to warningOccurred to indicate + * that the EOI marker is missing from the end of the stream. + * This usually signals that the stream is corrupted, but + * everything up to the last MCU should be usable. + */ + protected static final int WARNING_NO_EOI = 0; + + /** + * Warning code to be passed to warningOccurred to indicate + * that a JFIF segment was encountered inside a JFXX JPEG + * thumbnail and is being ignored. + */ + protected static final int WARNING_NO_JFIF_IN_THUMB = 1; + + /** + * Warning code to be passed to warningOccurred to indicate + * that embedded ICC profile is invalid and will be ignored. + */ + protected static final int WARNING_IGNORE_INVALID_ICC = 2; + + private static final int MAX_WARNING = WARNING_IGNORE_INVALID_ICC; + + /** + * Image index of image for which header information + * is available. + */ + private int currentImage = -1; + + // The following is copied out from C after reading the header. + // Unlike metadata, which may never be retrieved, we need this + // if we are to read an image at all. + + /** Set by setImageData native code callback */ + private int width; + /** Set by setImageData native code callback */ + private int height; + /** + * Set by setImageData native code callback. A modified + * IJG+NIFTY colorspace code. + */ + private int colorSpaceCode; + /** + * Set by setImageData native code callback. A modified + * IJG+NIFTY colorspace code. + */ + private int outColorSpaceCode; + /** Set by setImageData native code callback */ + private int numComponents; + /** Set by setImageData native code callback */ + private ColorSpace iccCS = null; + + + /** If we need to post-convert in Java, convert with this op */ + private ColorConvertOp convert = null; + + /** The image we are going to fill */ + private BufferedImage image = null; + + /** An intermediate Raster to hold decoded data */ + private WritableRaster raster = null; + + /** A view of our target Raster that we can setRect to */ + private WritableRaster target = null; + + /** The databuffer for the above Raster */ + private DataBufferByte buffer = null; + + /** The region in the destination where we will write pixels */ + private Rectangle destROI = null; + + /** The list of destination bands, if any */ + private int [] destinationBands = null; + + /** Stream metadata, cached, even when the stream is changed. */ + private JPEGMetadata streamMetadata = null; + + /** Image metadata, valid for the imageMetadataIndex only. */ + private JPEGMetadata imageMetadata = null; + private int imageMetadataIndex = -1; + + /** + * Set to true every time we seek in the stream; used to + * invalidate the native buffer contents in C. + */ + private boolean haveSeeked = false; + + /** + * Tables that have been read from a tables-only image at the + * beginning of a stream. + */ + private JPEGQTable [] abbrevQTables = null; + private JPEGHuffmanTable[] abbrevDCHuffmanTables = null; + private JPEGHuffmanTable[] abbrevACHuffmanTables = null; + + private int minProgressivePass = 0; + private int maxProgressivePass = Integer.MAX_VALUE; + + /** + * Variables used by progress monitoring. + */ + private static final int UNKNOWN = -1; // Number of passes + private static final int MIN_ESTIMATED_PASSES = 10; // IJG default + private int knownPassCount = UNKNOWN; + private int pass = 0; + private float percentToDate = 0.0F; + private float previousPassPercentage = 0.0F; + private int progInterval = 0; + + /** + * Set to true once stream has been checked for stream metadata + */ + private boolean tablesOnlyChecked = false; + + /** The referent to be registered with the Disposer. */ + private Object disposerReferent = new Object(); + + /** The DisposerRecord that handles the actual disposal of this reader. */ + private DisposerRecord disposerRecord; + + /** Sets up static C structures. */ + private static native void initReaderIDs(Class iisClass, + Class qTableClass, + Class huffClass); + + public JPEGImageReader(ImageReaderSpi originator) { + super(originator); + structPointer = initJPEGImageReader(); + disposerRecord = new JPEGReaderDisposerRecord(structPointer); + Disposer.addRecord(disposerReferent, disposerRecord); + } + + /** Sets up per-reader C structure and returns a pointer to it. */ + private native long initJPEGImageReader(); + + /** + * Called by the native code or other classes to signal a warning. + * The code is used to lookup a localized message to be used when + * sending warnings to listeners. + */ + protected void warningOccurred(int code) { + cbLock.lock(); + try { + if ((code < 0) || (code > MAX_WARNING)){ + throw new InternalError("Invalid warning index"); + } + processWarningOccurred + ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources", + Integer.toString(code)); + } finally { + cbLock.unlock(); + } + } + + /** + * The library has it's own error facility that emits warning messages. + * This routine is called by the native code when it has already + * formatted a string for output. + * XXX For truly complete localization of all warning messages, + * the sun_jpeg_output_message routine in the native code should + * send only the codes and parameters to a method here in Java, + * which will then format and send the warnings, using localized + * strings. This method will have to deal with all the parameters + * and formats (%u with possibly large numbers, %02d, %02x, etc.) + * that actually occur in the JPEG library. For now, this prevents + * library warnings from being printed to stderr. + */ + protected void warningWithMessage(String msg) { + cbLock.lock(); + try { + processWarningOccurred(msg); + } finally { + cbLock.unlock(); + } + } + + public void setInput(Object input, + boolean seekForwardOnly, + boolean ignoreMetadata) + { + setThreadLock(); + try { + cbLock.check(); + + super.setInput(input, seekForwardOnly, ignoreMetadata); + this.ignoreMetadata = ignoreMetadata; + resetInternalState(); + iis = (ImageInputStream) input; // Always works + setSource(structPointer); + } finally { + clearThreadLock(); + } + } + + /** + * This method is called from native code in order to fill + * native input buffer. + * + * We block any attempt to change the reading state during this + * method, in order to prevent a corruption of the native decoder + * state. + * + * @return number of bytes read from the stream. + */ + private int readInputData(byte[] buf, int off, int len) throws IOException { + cbLock.lock(); + try { + return iis.read(buf, off, len); + } finally { + cbLock.unlock(); + } + } + + /** + * This method is called from the native code in order to + * skip requested number of bytes in the input stream. + * + * @param n + * @return + * @throws IOException + */ + private long skipInputBytes(long n) throws IOException { + cbLock.lock(); + try { + return iis.skipBytes(n); + } finally { + cbLock.unlock(); + } + } + + private native void setSource(long structPointer); + + private void checkTablesOnly() throws IOException { + if (debug) { + System.out.println("Checking for tables-only image"); + } + long savePos = iis.getStreamPosition(); + if (debug) { + System.out.println("saved pos is " + savePos); + System.out.println("length is " + iis.length()); + } + // Read the first header + boolean tablesOnly = readNativeHeader(true); + if (tablesOnly) { + if (debug) { + System.out.println("tables-only image found"); + long pos = iis.getStreamPosition(); + System.out.println("pos after return from native is " + pos); + } + // This reads the tables-only image twice, once from C + // and once from Java, but only if ignoreMetadata is false + if (ignoreMetadata == false) { + iis.seek(savePos); + haveSeeked = true; + streamMetadata = new JPEGMetadata(true, false, + iis, this); + long pos = iis.getStreamPosition(); + if (debug) { + System.out.println + ("pos after constructing stream metadata is " + pos); + } + } + // Now we are at the first image if there are any, so add it + // to the list + if (hasNextImage()) { + imagePositions.add(new Long(iis.getStreamPosition())); + } + } else { // Not tables only, so add original pos to the list + imagePositions.add(new Long(savePos)); + // And set current image since we've read it now + currentImage = 0; + } + if (seekForwardOnly) { + Long pos = (Long) imagePositions.get(imagePositions.size()-1); + iis.flushBefore(pos.longValue()); + } + tablesOnlyChecked = true; + } + + public int getNumImages(boolean allowSearch) throws IOException { + setThreadLock(); + try { // locked thread + cbLock.check(); + + return getNumImagesOnThread(allowSearch); + } finally { + clearThreadLock(); + } + } + + private int getNumImagesOnThread(boolean allowSearch) + throws IOException { + if (numImages != 0) { + return numImages; + } + if (iis == null) { + throw new IllegalStateException("Input not set"); + } + if (allowSearch == true) { + if (seekForwardOnly) { + throw new IllegalStateException( + "seekForwardOnly and allowSearch can't both be true!"); + } + // Otherwise we have to read the entire stream + + if (!tablesOnlyChecked) { + checkTablesOnly(); + } + + iis.mark(); + + gotoImage(0); + + JPEGBuffer buffer = new JPEGBuffer(iis); + buffer.loadBuf(0); + + boolean done = false; + while (!done) { + done = buffer.scanForFF(this); + switch (buffer.buf[buffer.bufPtr] & 0xff) { + case JPEG.SOI: + numImages++; + // FALL THROUGH to decrement buffer vars + // This first set doesn't have a length + case 0: // not a marker, just a data 0xff + case JPEG.RST0: + case JPEG.RST1: + case JPEG.RST2: + case JPEG.RST3: + case JPEG.RST4: + case JPEG.RST5: + case JPEG.RST6: + case JPEG.RST7: + case JPEG.EOI: + buffer.bufAvail--; + buffer.bufPtr++; + break; + // All the others have a length + default: + buffer.bufAvail--; + buffer.bufPtr++; + buffer.loadBuf(2); + int length = ((buffer.buf[buffer.bufPtr++] & 0xff) << 8) | + (buffer.buf[buffer.bufPtr++] & 0xff); + buffer.bufAvail -= 2; + length -= 2; // length includes itself + buffer.skipData(length); + } + } + + + iis.reset(); + + return numImages; + } + + return -1; // Search is necessary for JPEG + } + + /** + * Sets the input stream to the start of the requested image. + *

+     * @exception IllegalStateException if the input source has not been
+     * set.
+     * @exception IndexOutOfBoundsException if the supplied index is
+     * out of bounds.
+     * 
+ */ + private void gotoImage(int imageIndex) throws IOException { + if (iis == null) { + throw new IllegalStateException("Input not set"); + } + if (imageIndex < minIndex) { + throw new IndexOutOfBoundsException(); + } + if (!tablesOnlyChecked) { + checkTablesOnly(); + } + if (imageIndex < imagePositions.size()) { + iis.seek(((Long)(imagePositions.get(imageIndex))).longValue()); + } else { + // read to start of image, saving positions + // First seek to the last position we already have, and skip the + // entire image + Long pos = (Long) imagePositions.get(imagePositions.size()-1); + iis.seek(pos.longValue()); + skipImage(); + // Now add all intervening positions, skipping images + for (int index = imagePositions.size(); + index <= imageIndex; + index++) { + // Is there an image? + if (!hasNextImage()) { + throw new IndexOutOfBoundsException(); + } + pos = new Long(iis.getStreamPosition()); + imagePositions.add(pos); + if (seekForwardOnly) { + iis.flushBefore(pos.longValue()); + } + if (index < imageIndex) { + skipImage(); + } // Otherwise we are where we want to be + } + } + + if (seekForwardOnly) { + minIndex = imageIndex; + } + + haveSeeked = true; // No way is native buffer still valid + } + + /** + * Skip over a complete image in the stream, leaving the stream + * positioned such that the next byte to be read is the first + * byte of the next image. For JPEG, this means that we read + * until we encounter an EOI marker or until the end of the stream. + * If the stream ends before an EOI marker is encountered, an + * IndexOutOfBoundsException is thrown. + */ + private void skipImage() throws IOException { + if (debug) { + System.out.println("skipImage called"); + } + boolean foundFF = false; + for (int byteval = iis.read(); + byteval != -1; + byteval = iis.read()) { + + if (foundFF == true) { + if (byteval == JPEG.EOI) { + return; + } + } + foundFF = (byteval == 0xff) ? true : false; + } + throw new IndexOutOfBoundsException(); + } + + /** + * Returns true if there is an image beyond + * the current stream position. Does not disturb the + * stream position. + */ + private boolean hasNextImage() throws IOException { + if (debug) { + System.out.print("hasNextImage called; returning "); + } + iis.mark(); + boolean foundFF = false; + for (int byteval = iis.read(); + byteval != -1; + byteval = iis.read()) { + + if (foundFF == true) { + if (byteval == JPEG.SOI) { + iis.reset(); + if (debug) { + System.out.println("true"); + } + return true; + } + } + foundFF = (byteval == 0xff) ? true : false; + } + // We hit the end of the stream before we hit an SOI, so no image + iis.reset(); + if (debug) { + System.out.println("false"); + } + return false; + } + + /** + * Push back the given number of bytes to the input stream. + * Called by the native code at the end of each image so + * that the next one can be identified from Java. + */ + private void pushBack(int num) throws IOException { + if (debug) { + System.out.println("pushing back " + num + " bytes"); + } + cbLock.lock(); + try { + iis.seek(iis.getStreamPosition()-num); + // The buffer is clear after this, so no need to set haveSeeked. + } finally { + cbLock.unlock(); + } + } + + /** + * Reads header information for the given image, if possible. + */ + private void readHeader(int imageIndex, boolean reset) + throws IOException { + gotoImage(imageIndex); + readNativeHeader(reset); // Ignore return + currentImage = imageIndex; + } + + private boolean readNativeHeader(boolean reset) throws IOException { + boolean retval = false; + retval = readImageHeader(structPointer, haveSeeked, reset); + haveSeeked = false; + return retval; + } + + /** + * Read in the header information starting from the current + * stream position, returning true if the + * header was a tables-only image. After this call, the + * native IJG decompression struct will contain the image + * information required by most query calls below + * (e.g. getWidth, getHeight, etc.), if the header was not + * a tables-only image. + * If reset is true, the state of the IJG + * object is reset so that it can read a header again. + * This happens automatically if the header was a tables-only + * image. + */ + private native boolean readImageHeader(long structPointer, + boolean clearBuffer, + boolean reset) + throws IOException; + + /* + * Called by the native code whenever an image header has been + * read. Whether we read metadata or not, we always need this + * information, so it is passed back independently of + * metadata, which may never be read. + */ + private void setImageData(int width, + int height, + int colorSpaceCode, + int outColorSpaceCode, + int numComponents, + byte [] iccData) { + this.width = width; + this.height = height; + this.colorSpaceCode = colorSpaceCode; + this.outColorSpaceCode = outColorSpaceCode; + this.numComponents = numComponents; + + if (iccData == null) { + iccCS = null; + return; + } + + ICC_Profile newProfile = null; + try { + newProfile = ICC_Profile.getInstance(iccData); + } catch (IllegalArgumentException e) { + /* + * Color profile data seems to be invalid. + * Ignore this profile. + */ + iccCS = null; + warningOccurred(WARNING_IGNORE_INVALID_ICC); + + return; + } + byte[] newData = newProfile.getData(); + + ICC_Profile oldProfile = null; + if (iccCS instanceof ICC_ColorSpace) { + oldProfile = ((ICC_ColorSpace)iccCS).getProfile(); + } + byte[] oldData = null; + if (oldProfile != null) { + oldData = oldProfile.getData(); + } + + /* + * At the moment we can't rely on the ColorSpace.equals() + * and ICC_Profile.equals() because they do not detect + * the case when two profiles are created from same data. + * + * So, we have to do data comparison in order to avoid + * creation of different ColorSpace instances for the same + * embedded data. + */ + if (oldData == null || + !java.util.Arrays.equals(oldData, newData)) + { + iccCS = new ICC_ColorSpace(newProfile); + // verify new color space + try { + float[] colors = iccCS.fromRGB(new float[] {1f, 0f, 0f}); + } catch (CMMException e) { + /* + * Embedded profile seems to be corrupted. + * Ignore this profile. + */ + iccCS = null; + cbLock.lock(); + try { + warningOccurred(WARNING_IGNORE_INVALID_ICC); + } finally { + cbLock.unlock(); + } + } + } + } + + public int getWidth(int imageIndex) throws IOException { + setThreadLock(); + try { + if (currentImage != imageIndex) { + cbLock.check(); + readHeader(imageIndex, true); + } + return width; + } finally { + clearThreadLock(); + } + } + + public int getHeight(int imageIndex) throws IOException { + setThreadLock(); + try { + if (currentImage != imageIndex) { + cbLock.check(); + readHeader(imageIndex, true); + } + return height; + } finally { + clearThreadLock(); + } + } + + /////////// Color Conversion and Image Types + + /** + * Return an ImageTypeSpecifier corresponding to the given + * color space code, or null if the color space is unsupported. + */ + private ImageTypeProducer getImageType(int code) { + ImageTypeProducer ret = null; + + if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) { + ret = ImageTypeProducer.getTypeProducer(code); + } + return ret; + } + + public ImageTypeSpecifier getRawImageType(int imageIndex) + throws IOException { + setThreadLock(); + try { + if (currentImage != imageIndex) { + cbLock.check(); + + readHeader(imageIndex, true); + } + + // Returns null if it can't be represented + return getImageType(colorSpaceCode).getType(); + } finally { + clearThreadLock(); + } + } + + public Iterator getImageTypes(int imageIndex) + throws IOException { + setThreadLock(); + try { + return getImageTypesOnThread(imageIndex); + } finally { + clearThreadLock(); + } + } + + private Iterator getImageTypesOnThread(int imageIndex) + throws IOException { + if (currentImage != imageIndex) { + cbLock.check(); + readHeader(imageIndex, true); + } + + // We return an iterator containing the default, any + // conversions that the library provides, and + // all the other default types with the same number + // of components, as we can do these as a post-process. + // As we convert Rasters rather than images, images + // with alpha cannot be converted in a post-process. + + // If this image can't be interpreted, this method + // returns an empty Iterator. + + // Get the raw ITS, if there is one. Note that this + // won't always be the same as the default. + ImageTypeProducer raw = getImageType(colorSpaceCode); + + // Given the encoded colorspace, build a list of ITS's + // representing outputs you could handle starting + // with the default. + + ArrayList list = new ArrayList(1); + + switch (colorSpaceCode) { + case JPEG.JCS_GRAYSCALE: + list.add(raw); + list.add(getImageType(JPEG.JCS_RGB)); + break; + case JPEG.JCS_RGB: + list.add(raw); + list.add(getImageType(JPEG.JCS_GRAYSCALE)); + list.add(getImageType(JPEG.JCS_YCC)); + break; + case JPEG.JCS_RGBA: + list.add(raw); + break; + case JPEG.JCS_YCC: + if (raw != null) { // Might be null if PYCC.pf not installed + list.add(raw); + list.add(getImageType(JPEG.JCS_RGB)); + } + break; + case JPEG.JCS_YCCA: + if (raw != null) { // Might be null if PYCC.pf not installed + list.add(raw); + } + break; + case JPEG.JCS_YCbCr: + // As there is no YCbCr ColorSpace, we can't support + // the raw type. + + // due to 4705399, use RGB as default in order to avoid + // slowing down of drawing operations with result image. + list.add(getImageType(JPEG.JCS_RGB)); + + if (iccCS != null) { + list.add(new ImageTypeProducer() { + protected ImageTypeSpecifier produce() { + return ImageTypeSpecifier.createInterleaved + (iccCS, + JPEG.bOffsRGB, // Assume it's for RGB + DataBuffer.TYPE_BYTE, + false, + false); + } + }); + + } + + list.add(getImageType(JPEG.JCS_GRAYSCALE)); + list.add(getImageType(JPEG.JCS_YCC)); + break; + case JPEG.JCS_YCbCrA: // Default is to convert to RGBA + // As there is no YCbCr ColorSpace, we can't support + // the raw type. + list.add(getImageType(JPEG.JCS_RGBA)); + break; + } + + return new ImageTypeIterator(list.iterator()); + } + + /** + * Checks the implied color conversion between the stream and + * the target image, altering the IJG output color space if necessary. + * If a java color conversion is required, then this sets up + * convert. + * If bands are being rearranged at all (either source or destination + * bands are specified in the param), then the default color + * conversions are assumed to be correct. + * Throws an IIOException if there is no conversion available. + */ + private void checkColorConversion(BufferedImage image, + ImageReadParam param) + throws IIOException { + + // If we are rearranging channels at all, the default + // conversions remain in place. If the user wants + // raw channels then he should do this while reading + // a Raster. + if (param != null) { + if ((param.getSourceBands() != null) || + (param.getDestinationBands() != null)) { + // Accept default conversions out of decoder, silently + return; + } + } + + // XXX - We do not currently support any indexed color models, + // though we could, as IJG will quantize for us. + // This is a performance and memory-use issue, as + // users can read RGB and then convert to indexed in Java. + + ColorModel cm = image.getColorModel(); + + if (cm instanceof IndexColorModel) { + throw new IIOException("IndexColorModel not supported"); + } + + // Now check the ColorSpace type against outColorSpaceCode + // We may want to tweak the default + ColorSpace cs = cm.getColorSpace(); + int csType = cs.getType(); + convert = null; + switch (outColorSpaceCode) { + case JPEG.JCS_GRAYSCALE: // Its gray in the file + if (csType == ColorSpace.TYPE_RGB) { // We want RGB + // IJG can do this for us more efficiently + setOutColorSpace(structPointer, JPEG.JCS_RGB); + // Update java state according to changes + // in the native part of decoder. + outColorSpaceCode = JPEG.JCS_RGB; + numComponents = 3; + } else if (csType != ColorSpace.TYPE_GRAY) { + throw new IIOException("Incompatible color conversion"); + } + break; + case JPEG.JCS_RGB: // IJG wants to go to RGB + if (csType == ColorSpace.TYPE_GRAY) { // We want gray + if (colorSpaceCode == JPEG.JCS_YCbCr) { + // If the jpeg space is YCbCr, IJG can do it + setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE); + // Update java state according to changes + // in the native part of decoder. + outColorSpaceCode = JPEG.JCS_GRAYSCALE; + numComponents = 1; + } + } else if ((iccCS != null) && + (cm.getNumComponents() == numComponents) && + (cs != iccCS)) { + // We have an ICC profile but it isn't used in the dest + // image. So convert from the profile cs to the target cs + convert = new ColorConvertOp(iccCS, cs, null); + // Leave IJG conversion in place; we still need it + } else if ((iccCS == null) && + (!cs.isCS_sRGB()) && + (cm.getNumComponents() == numComponents)) { + // Target isn't sRGB, so convert from sRGB to the target + convert = new ColorConvertOp(JPEG.JCS.sRGB, cs, null); + } else if (csType != ColorSpace.TYPE_RGB) { + throw new IIOException("Incompatible color conversion"); + } + break; + case JPEG.JCS_RGBA: + // No conversions available; image must be RGBA + if ((csType != ColorSpace.TYPE_RGB) || + (cm.getNumComponents() != numComponents)) { + throw new IIOException("Incompatible color conversion"); + } + break; + case JPEG.JCS_YCC: + { + ColorSpace YCC = JPEG.JCS.getYCC(); + if (YCC == null) { // We can't do YCC at all + throw new IIOException("Incompatible color conversion"); + } + if ((cs != YCC) && + (cm.getNumComponents() == numComponents)) { + convert = new ColorConvertOp(YCC, cs, null); + } + } + break; + case JPEG.JCS_YCCA: + { + ColorSpace YCC = JPEG.JCS.getYCC(); + // No conversions available; image must be YCCA + if ((YCC == null) || // We can't do YCC at all + (cs != YCC) || + (cm.getNumComponents() != numComponents)) { + throw new IIOException("Incompatible color conversion"); + } + } + break; + default: + // Anything else we can't handle at all + throw new IIOException("Incompatible color conversion"); + } + } + + /** + * Set the IJG output space to the given value. The library will + * perform the appropriate colorspace conversions. + */ + private native void setOutColorSpace(long structPointer, int id); + + /////// End of Color Conversion & Image Types + + public ImageReadParam getDefaultReadParam() { + return new JPEGImageReadParam(); + } + + public IIOMetadata getStreamMetadata() throws IOException { + setThreadLock(); + try { + if (!tablesOnlyChecked) { + cbLock.check(); + checkTablesOnly(); + } + return streamMetadata; + } finally { + clearThreadLock(); + } + } + + public IIOMetadata getImageMetadata(int imageIndex) + throws IOException { + setThreadLock(); + try { + // imageMetadataIndex will always be either a valid index or + // -1, in which case imageMetadata will not be null. + // So we can leave checking imageIndex for gotoImage. + if ((imageMetadataIndex == imageIndex) + && (imageMetadata != null)) { + return imageMetadata; + } + + cbLock.check(); + + gotoImage(imageIndex); + + imageMetadata = new JPEGMetadata(false, false, iis, this); + + imageMetadataIndex = imageIndex; + + return imageMetadata; + } finally { + clearThreadLock(); + } + } + + public BufferedImage read(int imageIndex, ImageReadParam param) + throws IOException { + setThreadLock(); + try { + cbLock.check(); + try { + readInternal(imageIndex, param, false); + } catch (RuntimeException e) { + resetLibraryState(structPointer); + throw e; + } catch (IOException e) { + resetLibraryState(structPointer); + throw e; + } + + BufferedImage ret = image; + image = null; // don't keep a reference here + return ret; + } finally { + clearThreadLock(); + } + } + + private Raster readInternal(int imageIndex, + ImageReadParam param, + boolean wantRaster) throws IOException { + readHeader(imageIndex, false); + + WritableRaster imRas = null; + int numImageBands = 0; + + if (!wantRaster){ + // Can we read this image? + Iterator imageTypes = getImageTypes(imageIndex); + if (imageTypes.hasNext() == false) { + throw new IIOException("Unsupported Image Type"); + } + + image = getDestination(param, imageTypes, width, height); + imRas = image.getRaster(); + + // The destination may still be incompatible. + + numImageBands = image.getSampleModel().getNumBands(); + + // Check whether we can handle any implied color conversion + + // Throws IIOException if the stream and the image are + // incompatible, and sets convert if a java conversion + // is necessary + checkColorConversion(image, param); + + // Check the source and destination bands in the param + checkReadParamBandSettings(param, numComponents, numImageBands); + } else { + // Set the output color space equal to the input colorspace + // This disables all conversions + setOutColorSpace(structPointer, colorSpaceCode); + image = null; + } + + // Create an intermediate 1-line Raster that will hold the decoded, + // subsampled, clipped, band-selected image data in a single + // byte-interleaved buffer. The above transformations + // will occur in C for performance. Every time this Raster + // is filled we will call back to acceptPixels below to copy + // this to whatever kind of buffer our image has. + + int [] srcBands = JPEG.bandOffsets[numComponents-1]; + int numRasterBands = (wantRaster ? numComponents : numImageBands); + destinationBands = null; + + Rectangle srcROI = new Rectangle(0, 0, 0, 0); + destROI = new Rectangle(0, 0, 0, 0); + computeRegions(param, width, height, image, srcROI, destROI); + + int periodX = 1; + int periodY = 1; + + minProgressivePass = 0; + maxProgressivePass = Integer.MAX_VALUE; + + if (param != null) { + periodX = param.getSourceXSubsampling(); + periodY = param.getSourceYSubsampling(); + + int[] sBands = param.getSourceBands(); + if (sBands != null) { + srcBands = sBands; + numRasterBands = srcBands.length; + } + if (!wantRaster) { // ignore dest bands for Raster + destinationBands = param.getDestinationBands(); + } + + minProgressivePass = param.getSourceMinProgressivePass(); + maxProgressivePass = param.getSourceMaxProgressivePass(); + + if (param instanceof JPEGImageReadParam) { + JPEGImageReadParam jparam = (JPEGImageReadParam) param; + if (jparam.areTablesSet()) { + abbrevQTables = jparam.getQTables(); + abbrevDCHuffmanTables = jparam.getDCHuffmanTables(); + abbrevACHuffmanTables = jparam.getACHuffmanTables(); + } + } + } + + int lineSize = destROI.width*numRasterBands; + + buffer = new DataBufferByte(lineSize); + + int [] bandOffs = JPEG.bandOffsets[numRasterBands-1]; + + raster = Raster.createInterleavedRaster(buffer, + destROI.width, 1, + lineSize, + numRasterBands, + bandOffs, + null); + + // Now that we have the Raster we'll decode to, get a view of the + // target Raster that will permit a simple setRect for each scanline + if (wantRaster) { + target = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, + destROI.width, + destROI.height, + lineSize, + numRasterBands, + bandOffs, + null); + } else { + target = imRas; + } + int [] bandSizes = target.getSampleModel().getSampleSize(); + for (int i = 0; i < bandSizes.length; i++) { + if (bandSizes[i] <= 0 || bandSizes[i] > 8) { + throw new IIOException("Illegal band size: should be 0 < size <= 8"); + } + } + + /* + * If the process is sequential, and we have restart markers, + * we could skip to the correct restart marker, if the library + * lets us. That's an optimization to investigate later. + */ + + // Check for update listeners (don't call back if none) + boolean callbackUpdates = ((updateListeners != null) + || (progressListeners != null)); + + // Set up progression data + initProgressData(); + // if we have a metadata object, we can count the scans + // and set knownPassCount + if (imageIndex == imageMetadataIndex) { // We have metadata + knownPassCount = 0; + for (Iterator iter = imageMetadata.markerSequence.iterator(); + iter.hasNext();) { + if (iter.next() instanceof SOSMarkerSegment) { + knownPassCount++; + } + } + } + progInterval = Math.max((target.getHeight()-1) / 20, 1); + if (knownPassCount > 0) { + progInterval *= knownPassCount; + } else if (maxProgressivePass != Integer.MAX_VALUE) { + progInterval *= (maxProgressivePass - minProgressivePass + 1); + } + + if (debug) { + System.out.println("**** Read Data *****"); + System.out.println("numRasterBands is " + numRasterBands); + System.out.print("srcBands:"); + for (int i = 0; i 0) && (y%progInterval == 0)) { + int height = target.getHeight()-1; + float percentOfPass = ((float)y)/height; + if (progressive) { + if (knownPassCount != UNKNOWN) { + processImageProgress((pass + percentOfPass)*100.0F + / knownPassCount); + } else if (maxProgressivePass != Integer.MAX_VALUE) { + // Use the range of allowed progressive passes + processImageProgress((pass + percentOfPass)*100.0F + / (maxProgressivePass - minProgressivePass + 1)); + } else { + // Assume there are a minimum of MIN_ESTIMATED_PASSES + // and that there is always one more pass + // Compute the percentage as the percentage at the end + // of the previous pass, plus the percentage of this + // pass scaled to be the percentage of the total remaining, + // assuming a minimum of MIN_ESTIMATED_PASSES passes and + // that there is always one more pass. This is monotonic + // and asymptotic to 1.0, which is what we need. + int remainingPasses = // including this one + Math.max(2, MIN_ESTIMATED_PASSES-pass); + int totalPasses = pass + remainingPasses-1; + progInterval = Math.max(height/20*totalPasses, + totalPasses); + if (y%progInterval == 0) { + percentToDate = previousPassPercentage + + (1.0F - previousPassPercentage) + * (percentOfPass)/remainingPasses; + if (debug) { + System.out.print("pass= " + pass); + System.out.print(", y= " + y); + System.out.print(", progInt= " + progInterval); + System.out.print(", % of pass: " + percentOfPass); + System.out.print(", rem. passes: " + + remainingPasses); + System.out.print(", prev%: " + + previousPassPercentage); + System.out.print(", %ToDate: " + percentToDate); + System.out.print(" "); + } + processImageProgress(percentToDate*100.0F); + } + } + } else { + processImageProgress(percentOfPass * 100.0F); + } + } + } finally { + cbLock.unlock(); + } + } + + private void initProgressData() { + knownPassCount = UNKNOWN; + pass = 0; + percentToDate = 0.0F; + previousPassPercentage = 0.0F; + progInterval = 0; + } + + private void passStarted (int pass) { + cbLock.lock(); + try { + this.pass = pass; + previousPassPercentage = percentToDate; + processPassStarted(image, + pass, + minProgressivePass, + maxProgressivePass, + 0, 0, + 1,1, + destinationBands); + } finally { + cbLock.unlock(); + } + } + + private void passComplete () { + cbLock.lock(); + try { + processPassComplete(image); + } finally { + cbLock.unlock(); + } + } + + void thumbnailStarted(int thumbnailIndex) { + cbLock.lock(); + try { + processThumbnailStarted(currentImage, thumbnailIndex); + } finally { + cbLock.unlock(); + } + } + + // Provide access to protected superclass method + void thumbnailProgress(float percentageDone) { + cbLock.lock(); + try { + processThumbnailProgress(percentageDone); + } finally { + cbLock.unlock(); + } + } + + // Provide access to protected superclass method + void thumbnailComplete() { + cbLock.lock(); + try { + processThumbnailComplete(); + } finally { + cbLock.unlock(); + } + } + + /** + * Returns true if the read was aborted. + */ + private native boolean readImage(long structPointer, + byte [] buffer, + int numRasterBands, + int [] srcBands, + int [] bandSizes, + int sourceXOffset, int sourceYOffset, + int sourceWidth, int sourceHeight, + int periodX, int periodY, + JPEGQTable [] abbrevQTables, + JPEGHuffmanTable [] abbrevDCHuffmanTables, + JPEGHuffmanTable [] abbrevACHuffmanTables, + int minProgressivePass, + int maxProgressivePass, + boolean wantUpdates); + + public void abort() { + setThreadLock(); + try { + /** + * NB: we do not check the call back lock here, + * we allow to abort the reader any time. + */ + + super.abort(); + abortRead(structPointer); + } finally { + clearThreadLock(); + } + } + + /** Set the C level abort flag. Keep it atomic for thread safety. */ + private native void abortRead(long structPointer); + + /** Resets library state when an exception occurred during a read. */ + private native void resetLibraryState(long structPointer); + + public boolean canReadRaster() { + return true; + } + + public Raster readRaster(int imageIndex, ImageReadParam param) + throws IOException { + setThreadLock(); + Raster retval = null; + try { + cbLock.check(); + /* + * This could be further optimized by not resetting the dest. + * offset and creating a translated raster in readInternal() + * (see bug 4994702 for more info). + */ + + // For Rasters, destination offset is logical, not physical, so + // set it to 0 before calling computeRegions, so that the destination + // region is not clipped. + Point saveDestOffset = null; + if (param != null) { + saveDestOffset = param.getDestinationOffset(); + param.setDestinationOffset(new Point(0, 0)); + } + retval = readInternal(imageIndex, param, true); + // Apply the destination offset, if any, as a logical offset + if (saveDestOffset != null) { + target = target.createWritableTranslatedChild(saveDestOffset.x, + saveDestOffset.y); + } + } catch (RuntimeException e) { + resetLibraryState(structPointer); + throw e; + } catch (IOException e) { + resetLibraryState(structPointer); + throw e; + } finally { + clearThreadLock(); + } + return retval; + } + + public boolean readerSupportsThumbnails() { + return true; + } + + public int getNumThumbnails(int imageIndex) throws IOException { + setThreadLock(); + try { + cbLock.check(); + + getImageMetadata(imageIndex); // checks iis state for us + // Now check the jfif segments + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) imageMetadata.findMarkerSegment + (JFIFMarkerSegment.class, true); + int retval = 0; + if (jfif != null) { + retval = (jfif.thumb == null) ? 0 : 1; + retval += jfif.extSegments.size(); + } + return retval; + } finally { + clearThreadLock(); + } + } + + public int getThumbnailWidth(int imageIndex, int thumbnailIndex) + throws IOException { + setThreadLock(); + try { + cbLock.check(); + + if ((thumbnailIndex < 0) + || (thumbnailIndex >= getNumThumbnails(imageIndex))) { + throw new IndexOutOfBoundsException("No such thumbnail"); + } + // Now we know that there is a jfif segment + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) imageMetadata.findMarkerSegment + (JFIFMarkerSegment.class, true); + return jfif.getThumbnailWidth(thumbnailIndex); + } finally { + clearThreadLock(); + } + } + + public int getThumbnailHeight(int imageIndex, int thumbnailIndex) + throws IOException { + setThreadLock(); + try { + cbLock.check(); + + if ((thumbnailIndex < 0) + || (thumbnailIndex >= getNumThumbnails(imageIndex))) { + throw new IndexOutOfBoundsException("No such thumbnail"); + } + // Now we know that there is a jfif segment + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) imageMetadata.findMarkerSegment + (JFIFMarkerSegment.class, true); + return jfif.getThumbnailHeight(thumbnailIndex); + } finally { + clearThreadLock(); + } + } + + public BufferedImage readThumbnail(int imageIndex, + int thumbnailIndex) + throws IOException { + setThreadLock(); + try { + cbLock.check(); + + if ((thumbnailIndex < 0) + || (thumbnailIndex >= getNumThumbnails(imageIndex))) { + throw new IndexOutOfBoundsException("No such thumbnail"); + } + // Now we know that there is a jfif segment and that iis is good + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) imageMetadata.findMarkerSegment + (JFIFMarkerSegment.class, true); + return jfif.getThumbnail(iis, thumbnailIndex, this); + } finally { + clearThreadLock(); + } + } + + private void resetInternalState() { + // reset C structures + resetReader(structPointer); + + // reset local Java structures + numImages = 0; + imagePositions = new ArrayList(); + currentImage = -1; + image = null; + raster = null; + target = null; + buffer = null; + destROI = null; + destinationBands = null; + streamMetadata = null; + imageMetadata = null; + imageMetadataIndex = -1; + haveSeeked = false; + tablesOnlyChecked = false; + iccCS = null; + initProgressData(); + } + + public void reset() { + setThreadLock(); + try { + cbLock.check(); + super.reset(); + } finally { + clearThreadLock(); + } + } + + private native void resetReader(long structPointer); + + public void dispose() { + setThreadLock(); + try { + cbLock.check(); + + if (structPointer != 0) { + disposerRecord.dispose(); + structPointer = 0; + } + } finally { + clearThreadLock(); + } + } + + private static native void disposeReader(long structPointer); + + private static class JPEGReaderDisposerRecord implements DisposerRecord { + private long pData; + + public JPEGReaderDisposerRecord(long pData) { + this.pData = pData; + } + + public synchronized void dispose() { + if (pData != 0) { + disposeReader(pData); + pData = 0; + } + } + } + + private Thread theThread = null; + private int theLockCount = 0; + + private synchronized void setThreadLock() { + Thread currThread = Thread.currentThread(); + if (theThread != null) { + if (theThread != currThread) { + // it looks like that this reader instance is used + // by multiple threads. + throw new IllegalStateException("Attempt to use instance of " + + this + " locked on thread " + + theThread + " from thread " + + currThread); + } else { + theLockCount ++; + } + } else { + theThread = currThread; + theLockCount = 1; + } + } + + private synchronized void clearThreadLock() { + Thread currThread = Thread.currentThread(); + if (theThread == null || theThread != currThread) { + throw new IllegalStateException("Attempt to clear thread lock " + + " form wrong thread." + + " Locked thread: " + theThread + + "; current thread: " + currThread); + } + theLockCount --; + if (theLockCount == 0) { + theThread = null; + } + } + + private CallBackLock cbLock = new CallBackLock(); + + private static class CallBackLock { + + private State lockState; + + CallBackLock() { + lockState = State.Unlocked; + } + + void check() { + if (lockState != State.Unlocked) { + throw new IllegalStateException("Access to the reader is not allowed"); + } + } + + private void lock() { + lockState = State.Locked; + } + + private void unlock() { + lockState = State.Unlocked; + } + + private static enum State { + Unlocked, + Locked + } + } +} + +/** + * An internal helper class that wraps producer's iterator + * and extracts specifier instances on demand. + */ +class ImageTypeIterator implements Iterator { + private Iterator producers; + private ImageTypeSpecifier theNext = null; + + public ImageTypeIterator(Iterator producers) { + this.producers = producers; + } + + public boolean hasNext() { + if (theNext != null) { + return true; + } + if (!producers.hasNext()) { + return false; + } + do { + theNext = producers.next().getType(); + } while (theNext == null && producers.hasNext()); + + return (theNext != null); + } + + public ImageTypeSpecifier next() { + if (theNext != null || hasNext()) { + ImageTypeSpecifier t = theNext; + theNext = null; + return t; + } else { + throw new NoSuchElementException(); + } + } + + public void remove() { + producers.remove(); + } +} + +/** + * An internal helper class that provides means for deferred creation + * of ImageTypeSpecifier instance required to describe available + * destination types. + * + * This implementation only supports standard + * jpeg color spaces (defined by corresponding JCS color space code). + * + * To support other color spaces one can override produce() method to + * return custom instance of ImageTypeSpecifier. + */ +class ImageTypeProducer { + + private ImageTypeSpecifier type = null; + boolean failed = false; + private int csCode; + + public ImageTypeProducer(int csCode) { + this.csCode = csCode; + } + + public ImageTypeProducer() { + csCode = -1; // undefined + } + + public synchronized ImageTypeSpecifier getType() { + if (!failed && type == null) { + try { + type = produce(); + } catch (Throwable e) { + failed = true; + } + } + return type; + } + + private static final ImageTypeProducer [] defaultTypes = + new ImageTypeProducer [JPEG.NUM_JCS_CODES]; + + public synchronized static ImageTypeProducer getTypeProducer(int csCode) { + if (csCode < 0 || csCode >= JPEG.NUM_JCS_CODES) { + return null; + } + if (defaultTypes[csCode] == null) { + defaultTypes[csCode] = new ImageTypeProducer(csCode); + } + return defaultTypes[csCode]; + } + + protected ImageTypeSpecifier produce() { + switch (csCode) { + case JPEG.JCS_GRAYSCALE: + return ImageTypeSpecifier.createFromBufferedImageType + (BufferedImage.TYPE_BYTE_GRAY); + case JPEG.JCS_RGB: + return ImageTypeSpecifier.createInterleaved(JPEG.JCS.sRGB, + JPEG.bOffsRGB, + DataBuffer.TYPE_BYTE, + false, + false); + case JPEG.JCS_RGBA: + return ImageTypeSpecifier.createPacked(JPEG.JCS.sRGB, + 0xff000000, + 0x00ff0000, + 0x0000ff00, + 0x000000ff, + DataBuffer.TYPE_INT, + false); + case JPEG.JCS_YCC: + if (JPEG.JCS.getYCC() != null) { + return ImageTypeSpecifier.createInterleaved( + JPEG.JCS.getYCC(), + JPEG.bandOffsets[2], + DataBuffer.TYPE_BYTE, + false, + false); + } else { + return null; + } + case JPEG.JCS_YCCA: + if (JPEG.JCS.getYCC() != null) { + return ImageTypeSpecifier.createInterleaved( + JPEG.JCS.getYCC(), + JPEG.bandOffsets[3], + DataBuffer.TYPE_BYTE, + true, + false); + } else { + return null; + } + default: + return null; + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReaderResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReaderResources.java new file mode 100644 index 000000000..e4b09197a --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReaderResources.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import java.util.ListResourceBundle; + +public class JPEGImageReaderResources extends ListResourceBundle { + + public JPEGImageReaderResources() {} + + protected Object[][] getContents() { + return new Object[][] { + + {Integer.toString(JPEGImageReader.WARNING_NO_EOI), + "Truncated File - Missing EOI marker"}, + {Integer.toString(JPEGImageReader.WARNING_NO_JFIF_IN_THUMB), + "JFIF markers not allowed in JFIF JPEG thumbnail; ignored"}, + {Integer.toString(JPEGImageReader.WARNING_IGNORE_INVALID_ICC), + "Embedded color profile is invalid; ignored"} + + }; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReaderSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReaderSpi.java new file mode 100644 index 000000000..8d2569e46 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageReaderSpi.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2000, 2004, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import java.util.Locale; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ServiceRegistry; +import java.io.IOException; +import javax.imageio.ImageReader; +import javax.imageio.IIOException; + +public class JPEGImageReaderSpi extends ImageReaderSpi { + + private static String [] writerSpiNames = + {"com.sun.imageio.plugins.jpeg.JPEGImageWriterSpi"}; + + public JPEGImageReaderSpi() { + super(JPEG.vendor, + JPEG.version, + JPEG.names, + JPEG.suffixes, + JPEG.MIMETypes, + "com.sun.imageio.plugins.jpeg.JPEGImageReader", + new Class[] { ImageInputStream.class }, + writerSpiNames, + true, + JPEG.nativeStreamMetadataFormatName, + JPEG.nativeStreamMetadataFormatClassName, + null, null, + true, + JPEG.nativeImageMetadataFormatName, + JPEG.nativeImageMetadataFormatClassName, + null, null + ); + } + + public String getDescription(Locale locale) { + return "Standard JPEG Image Reader"; + } + + public boolean canDecodeInput(Object source) throws IOException { + if (!(source instanceof ImageInputStream)) { + return false; + } + ImageInputStream iis = (ImageInputStream) source; + iis.mark(); + // If the first two bytes are a JPEG SOI marker, it's probably + // a JPEG file. If they aren't, it definitely isn't a JPEG file. + int byte1 = iis.read(); + int byte2 = iis.read(); + iis.reset(); + if ((byte1 == 0xFF) && (byte2 == JPEG.SOI)) { + return true; + } + return false; + } + + public ImageReader createReaderInstance(Object extension) + throws IIOException { + return new JPEGImageReader(this); + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriter.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriter.java new file mode 100644 index 000000000..fc5607696 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriter.java @@ -0,0 +1,1913 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.IIOException; +import javax.imageio.ImageWriter; +import javax.imageio.ImageWriteParam; +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; +import javax.imageio.plugins.jpeg.JPEGQTable; +import javax.imageio.plugins.jpeg.JPEGHuffmanTable; + +import org.w3c.dom.Node; + +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.SampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.ColorConvertOp; +import java.awt.image.RenderedImage; +import java.awt.image.BufferedImage; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.Transparency; + +import java.io.IOException; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; + +public class JPEGImageWriter extends ImageWriter { + + ///////// Private variables + + private boolean debug = false; + + /** + * The following variable contains a pointer to the IJG library + * structure for this reader. It is assigned in the constructor + * and then is passed in to every native call. It is set to 0 + * by dispose to avoid disposing twice. + */ + private long structPointer = 0; + + + /** The output stream we write to */ + private ImageOutputStream ios = null; + + /** The Raster we will write from */ + private Raster srcRas = null; + + /** An intermediate Raster holding compressor-friendly data */ + private WritableRaster raster = null; + + /** + * Set to true if we are writing an image with an + * indexed ColorModel + */ + private boolean indexed = false; + private IndexColorModel indexCM = null; + + private boolean convertTosRGB = false; // Used by PhotoYCC only + private WritableRaster converted = null; + + private boolean isAlphaPremultiplied = false; + private ColorModel srcCM = null; + + /** + * If there are thumbnails to be written, this is the list. + */ + private List thumbnails = null; + + /** + * If metadata should include an icc profile, store it here. + */ + private ICC_Profile iccProfile = null; + + private int sourceXOffset = 0; + private int sourceYOffset = 0; + private int sourceWidth = 0; + private int [] srcBands = null; + private int sourceHeight = 0; + + /** Used when calling listeners */ + private int currentImage = 0; + + private ColorConvertOp convertOp = null; + + private JPEGQTable [] streamQTables = null; + private JPEGHuffmanTable[] streamDCHuffmanTables = null; + private JPEGHuffmanTable[] streamACHuffmanTables = null; + + // Parameters for writing metadata + private boolean ignoreJFIF = false; // If it's there, use it + private boolean forceJFIF = false; // Add one for the thumbnails + private boolean ignoreAdobe = false; // If it's there, use it + private int newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; // Change if needed + private boolean writeDefaultJFIF = false; + private boolean writeAdobe = false; + private JPEGMetadata metadata = null; + + private boolean sequencePrepared = false; + + private int numScans = 0; + + /** The referent to be registered with the Disposer. */ + private Object disposerReferent = new Object(); + + /** The DisposerRecord that handles the actual disposal of this writer. */ + private DisposerRecord disposerRecord; + + ///////// End of Private variables + + ///////// Protected variables + + protected static final int WARNING_DEST_IGNORED = 0; + protected static final int WARNING_STREAM_METADATA_IGNORED = 1; + protected static final int WARNING_DEST_METADATA_COMP_MISMATCH = 2; + protected static final int WARNING_DEST_METADATA_JFIF_MISMATCH = 3; + protected static final int WARNING_DEST_METADATA_ADOBE_MISMATCH = 4; + protected static final int WARNING_IMAGE_METADATA_JFIF_MISMATCH = 5; + protected static final int WARNING_IMAGE_METADATA_ADOBE_MISMATCH = 6; + protected static final int WARNING_METADATA_NOT_JPEG_FOR_RASTER = 7; + protected static final int WARNING_NO_BANDS_ON_INDEXED = 8; + protected static final int WARNING_ILLEGAL_THUMBNAIL = 9; + protected static final int WARNING_IGNORING_THUMBS = 10; + protected static final int WARNING_FORCING_JFIF = 11; + protected static final int WARNING_THUMB_CLIPPED = 12; + protected static final int WARNING_METADATA_ADJUSTED_FOR_THUMB = 13; + protected static final int WARNING_NO_RGB_THUMB_AS_INDEXED = 14; + protected static final int WARNING_NO_GRAY_THUMB_AS_INDEXED = 15; + + private static final int MAX_WARNING = WARNING_NO_GRAY_THUMB_AS_INDEXED; + + ///////// End of Protected variables + + ///////// static initializer + + static { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Void run() { + System.loadLibrary("jpeg"); + return null; + } + }); + initWriterIDs(JPEGQTable.class, + JPEGHuffmanTable.class); + } + + //////// Public API + + public JPEGImageWriter(ImageWriterSpi originator) { + super(originator); + structPointer = initJPEGImageWriter(); + disposerRecord = new JPEGWriterDisposerRecord(structPointer); + Disposer.addRecord(disposerReferent, disposerRecord); + } + + public void setOutput(Object output) { + setThreadLock(); + try { + cbLock.check(); + + super.setOutput(output); // validates output + resetInternalState(); + ios = (ImageOutputStream) output; // so this will always work + // Set the native destination + setDest(structPointer); + } finally { + clearThreadLock(); + } + } + + public ImageWriteParam getDefaultWriteParam() { + return new JPEGImageWriteParam(null); + } + + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { + setThreadLock(); + try { + return new JPEGMetadata(param, this); + } finally { + clearThreadLock(); + } + } + + public IIOMetadata + getDefaultImageMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param) { + setThreadLock(); + try { + return new JPEGMetadata(imageType, param, this); + } finally { + clearThreadLock(); + } + } + + public IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param) { + // There isn't much we can do. If it's one of ours, then + // return it. Otherwise just return null. We use it only + // for tables, so we can't get a default and modify it, + // as this will usually not be what is intended. + if (inData instanceof JPEGMetadata) { + JPEGMetadata jpegData = (JPEGMetadata) inData; + if (jpegData.isStream) { + return inData; + } + } + return null; + } + + public IIOMetadata + convertImageMetadata(IIOMetadata inData, + ImageTypeSpecifier imageType, + ImageWriteParam param) { + setThreadLock(); + try { + return convertImageMetadataOnThread(inData, imageType, param); + } finally { + clearThreadLock(); + } + } + + private IIOMetadata + convertImageMetadataOnThread(IIOMetadata inData, + ImageTypeSpecifier imageType, + ImageWriteParam param) { + // If it's one of ours, just return it + if (inData instanceof JPEGMetadata) { + JPEGMetadata jpegData = (JPEGMetadata) inData; + if (!jpegData.isStream) { + return inData; + } else { + // Can't convert stream metadata to image metadata + // XXX Maybe this should put out a warning? + return null; + } + } + // If it's not one of ours, create a default and set it from + // the standard tree from the input, if it exists. + if (inData.isStandardMetadataFormatSupported()) { + String formatName = + IIOMetadataFormatImpl.standardMetadataFormatName; + Node tree = inData.getAsTree(formatName); + if (tree != null) { + JPEGMetadata jpegData = new JPEGMetadata(imageType, + param, + this); + try { + jpegData.setFromTree(formatName, tree); + } catch (IIOInvalidTreeException e) { + // Other plug-in generates bogus standard tree + // XXX Maybe this should put out a warning? + return null; + } + + return jpegData; + } + } + return null; + } + + public int getNumThumbnailsSupported(ImageTypeSpecifier imageType, + ImageWriteParam param, + IIOMetadata streamMetadata, + IIOMetadata imageMetadata) { + if (jfifOK(imageType, param, streamMetadata, imageMetadata)) { + return Integer.MAX_VALUE; + } + return 0; + } + + static final Dimension [] preferredThumbSizes = {new Dimension(1, 1), + new Dimension(255, 255)}; + + public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType, + ImageWriteParam param, + IIOMetadata streamMetadata, + IIOMetadata imageMetadata) { + if (jfifOK(imageType, param, streamMetadata, imageMetadata)) { + return (Dimension [])preferredThumbSizes.clone(); + } + return null; + } + + private boolean jfifOK(ImageTypeSpecifier imageType, + ImageWriteParam param, + IIOMetadata streamMetadata, + IIOMetadata imageMetadata) { + // If the image type and metadata are JFIF compatible, return true + if ((imageType != null) && + (!JPEG.isJFIFcompliant(imageType, true))) { + return false; + } + if (imageMetadata != null) { + JPEGMetadata metadata = null; + if (imageMetadata instanceof JPEGMetadata) { + metadata = (JPEGMetadata) imageMetadata; + } else { + metadata = (JPEGMetadata)convertImageMetadata(imageMetadata, + imageType, + param); + } + // metadata must have a jfif node + if (metadata.findMarkerSegment + (JFIFMarkerSegment.class, true) == null){ + return false; + } + } + return true; + } + + public boolean canWriteRasters() { + return true; + } + + public void write(IIOMetadata streamMetadata, + IIOImage image, + ImageWriteParam param) throws IOException { + setThreadLock(); + try { + cbLock.check(); + + writeOnThread(streamMetadata, image, param); + } finally { + clearThreadLock(); + } + } + + private void writeOnThread(IIOMetadata streamMetadata, + IIOImage image, + ImageWriteParam param) throws IOException { + + if (ios == null) { + throw new IllegalStateException("Output has not been set!"); + } + + if (image == null) { + throw new IllegalArgumentException("image is null!"); + } + + // if streamMetadata is not null, issue a warning + if (streamMetadata != null) { + warningOccurred(WARNING_STREAM_METADATA_IGNORED); + } + + // Obtain the raster and image, if there is one + boolean rasterOnly = image.hasRaster(); + + RenderedImage rimage = null; + if (rasterOnly) { + srcRas = image.getRaster(); + } else { + rimage = image.getRenderedImage(); + if (rimage instanceof BufferedImage) { + // Use the Raster directly. + srcRas = ((BufferedImage)rimage).getRaster(); + } else if (rimage.getNumXTiles() == 1 && + rimage.getNumYTiles() == 1) + { + // Get the unique tile. + srcRas = rimage.getTile(rimage.getMinTileX(), + rimage.getMinTileY()); + + // Ensure the Raster has dimensions of the image, + // as the tile dimensions might differ. + if (srcRas.getWidth() != rimage.getWidth() || + srcRas.getHeight() != rimage.getHeight()) + { + srcRas = srcRas.createChild(srcRas.getMinX(), + srcRas.getMinY(), + rimage.getWidth(), + rimage.getHeight(), + srcRas.getMinX(), + srcRas.getMinY(), + null); + } + } else { + // Image is tiled so get a contiguous raster by copying. + srcRas = rimage.getData(); + } + } + + // Now determine if we are using a band subset + + // By default, we are using all source bands + int numSrcBands = srcRas.getNumBands(); + indexed = false; + indexCM = null; + ColorModel cm = null; + ColorSpace cs = null; + isAlphaPremultiplied = false; + srcCM = null; + if (!rasterOnly) { + cm = rimage.getColorModel(); + if (cm != null) { + cs = cm.getColorSpace(); + if (cm instanceof IndexColorModel) { + indexed = true; + indexCM = (IndexColorModel) cm; + numSrcBands = cm.getNumComponents(); + } + if (cm.isAlphaPremultiplied()) { + isAlphaPremultiplied = true; + srcCM = cm; + } + } + } + + srcBands = JPEG.bandOffsets[numSrcBands-1]; + int numBandsUsed = numSrcBands; + // Consult the param to determine if we're writing a subset + + if (param != null) { + int[] sBands = param.getSourceBands(); + if (sBands != null) { + if (indexed) { + warningOccurred(WARNING_NO_BANDS_ON_INDEXED); + } else { + srcBands = sBands; + numBandsUsed = srcBands.length; + if (numBandsUsed > numSrcBands) { + throw new IIOException + ("ImageWriteParam specifies too many source bands"); + } + } + } + } + + boolean usingBandSubset = (numBandsUsed != numSrcBands); + boolean fullImage = ((!rasterOnly) && (!usingBandSubset)); + + int [] bandSizes = null; + if (!indexed) { + bandSizes = srcRas.getSampleModel().getSampleSize(); + // If this is a subset, we must adjust bandSizes + if (usingBandSubset) { + int [] temp = new int [numBandsUsed]; + for (int i = 0; i < numBandsUsed; i++) { + temp[i] = bandSizes[srcBands[i]]; + } + bandSizes = temp; + } + } else { + int [] tempSize = srcRas.getSampleModel().getSampleSize(); + bandSizes = new int [numSrcBands]; + for (int i = 0; i < numSrcBands; i++) { + bandSizes[i] = tempSize[0]; // All the same + } + } + + for (int i = 0; i < bandSizes.length; i++) { + // 4450894 part 1: The IJG libraries are compiled so they only + // handle <= 8-bit samples. We now check the band sizes and throw + // an exception for images, such as USHORT_GRAY, with > 8 bits + // per sample. + if (bandSizes[i] <= 0 || bandSizes[i] > 8) { + throw new IIOException("Illegal band size: should be 0 < size <= 8"); + } + // 4450894 part 2: We expand IndexColorModel images to full 24- + // or 32-bit in grabPixels() for each scanline. For indexed + // images such as BYTE_BINARY, we need to ensure that we update + // bandSizes to account for the scaling from 1-bit band sizes + // to 8-bit. + if (indexed) { + bandSizes[i] = 8; + } + } + + if (debug) { + System.out.println("numSrcBands is " + numSrcBands); + System.out.println("numBandsUsed is " + numBandsUsed); + System.out.println("usingBandSubset is " + usingBandSubset); + System.out.println("fullImage is " + fullImage); + System.out.print("Band sizes:"); + for (int i = 0; i< bandSizes.length; i++) { + System.out.print(" " + bandSizes[i]); + } + System.out.println(); + } + + // Destination type, if there is one + ImageTypeSpecifier destType = null; + if (param != null) { + destType = param.getDestinationType(); + // Ignore dest type if we are writing a complete image + if ((fullImage) && (destType != null)) { + warningOccurred(WARNING_DEST_IGNORED); + destType = null; + } + } + + // Examine the param + + sourceXOffset = srcRas.getMinX(); + sourceYOffset = srcRas.getMinY(); + int imageWidth = srcRas.getWidth(); + int imageHeight = srcRas.getHeight(); + sourceWidth = imageWidth; + sourceHeight = imageHeight; + int periodX = 1; + int periodY = 1; + int gridX = 0; + int gridY = 0; + JPEGQTable [] qTables = null; + JPEGHuffmanTable[] DCHuffmanTables = null; + JPEGHuffmanTable[] ACHuffmanTables = null; + boolean optimizeHuffman = false; + JPEGImageWriteParam jparam = null; + int progressiveMode = ImageWriteParam.MODE_DISABLED; + + if (param != null) { + + Rectangle sourceRegion = param.getSourceRegion(); + if (sourceRegion != null) { + Rectangle imageBounds = new Rectangle(sourceXOffset, + sourceYOffset, + sourceWidth, + sourceHeight); + sourceRegion = sourceRegion.intersection(imageBounds); + sourceXOffset = sourceRegion.x; + sourceYOffset = sourceRegion.y; + sourceWidth = sourceRegion.width; + sourceHeight = sourceRegion.height; + } + + if (sourceWidth + sourceXOffset > imageWidth) { + sourceWidth = imageWidth - sourceXOffset; + } + if (sourceHeight + sourceYOffset > imageHeight) { + sourceHeight = imageHeight - sourceYOffset; + } + + periodX = param.getSourceXSubsampling(); + periodY = param.getSourceYSubsampling(); + gridX = param.getSubsamplingXOffset(); + gridY = param.getSubsamplingYOffset(); + + switch(param.getCompressionMode()) { + case ImageWriteParam.MODE_DISABLED: + throw new IIOException("JPEG compression cannot be disabled"); + case ImageWriteParam.MODE_EXPLICIT: + float quality = param.getCompressionQuality(); + quality = JPEG.convertToLinearQuality(quality); + qTables = new JPEGQTable[2]; + qTables[0] = JPEGQTable.K1Luminance.getScaledInstance + (quality, true); + qTables[1] = JPEGQTable.K2Chrominance.getScaledInstance + (quality, true); + break; + case ImageWriteParam.MODE_DEFAULT: + qTables = new JPEGQTable[2]; + qTables[0] = JPEGQTable.K1Div2Luminance; + qTables[1] = JPEGQTable.K2Div2Chrominance; + break; + // We'll handle the metadata case later + } + + progressiveMode = param.getProgressiveMode(); + + if (param instanceof JPEGImageWriteParam) { + jparam = (JPEGImageWriteParam)param; + optimizeHuffman = jparam.getOptimizeHuffmanTables(); + } + } + + // Now examine the metadata + IIOMetadata mdata = image.getMetadata(); + if (mdata != null) { + if (mdata instanceof JPEGMetadata) { + metadata = (JPEGMetadata) mdata; + if (debug) { + System.out.println + ("We have metadata, and it's JPEG metadata"); + } + } else { + if (!rasterOnly) { + ImageTypeSpecifier type = destType; + if (type == null) { + type = new ImageTypeSpecifier(rimage); + } + metadata = (JPEGMetadata) convertImageMetadata(mdata, + type, + param); + } else { + warningOccurred(WARNING_METADATA_NOT_JPEG_FOR_RASTER); + } + } + } + + // First set a default state + + ignoreJFIF = false; // If it's there, use it + ignoreAdobe = false; // If it's there, use it + newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; // Change if needed + writeDefaultJFIF = false; + writeAdobe = false; + + // By default we'll do no conversion: + int inCsType = JPEG.JCS_UNKNOWN; + int outCsType = JPEG.JCS_UNKNOWN; + + JFIFMarkerSegment jfif = null; + AdobeMarkerSegment adobe = null; + SOFMarkerSegment sof = null; + + if (metadata != null) { + jfif = (JFIFMarkerSegment) metadata.findMarkerSegment + (JFIFMarkerSegment.class, true); + adobe = (AdobeMarkerSegment) metadata.findMarkerSegment + (AdobeMarkerSegment.class, true); + sof = (SOFMarkerSegment) metadata.findMarkerSegment + (SOFMarkerSegment.class, true); + } + + iccProfile = null; // By default don't write one + convertTosRGB = false; // PhotoYCC does this + converted = null; + + if (destType != null) { + if (numBandsUsed != destType.getNumBands()) { + throw new IIOException + ("Number of source bands != number of destination bands"); + } + cs = destType.getColorModel().getColorSpace(); + // Check the metadata against the destination type + if (metadata != null) { + checkSOFBands(sof, numBandsUsed); + + checkJFIF(jfif, destType, false); + // Do we want to write an ICC profile? + if ((jfif != null) && (ignoreJFIF == false)) { + if (JPEG.isNonStandardICC(cs)) { + iccProfile = ((ICC_ColorSpace) cs).getProfile(); + } + } + checkAdobe(adobe, destType, false); + + } else { // no metadata, but there is a dest type + // If we can add a JFIF or an Adobe marker segment, do so + if (JPEG.isJFIFcompliant(destType, false)) { + writeDefaultJFIF = true; + // Do we want to write an ICC profile? + if (JPEG.isNonStandardICC(cs)) { + iccProfile = ((ICC_ColorSpace) cs).getProfile(); + } + } else { + int transform = JPEG.transformForType(destType, false); + if (transform != JPEG.ADOBE_IMPOSSIBLE) { + writeAdobe = true; + newAdobeTransform = transform; + } + } + // re-create the metadata + metadata = new JPEGMetadata(destType, null, this); + } + inCsType = getSrcCSType(destType); + outCsType = getDefaultDestCSType(destType); + } else { // no destination type + if (metadata == null) { + if (fullImage) { // no dest, no metadata, full image + // Use default metadata matching the image and param + metadata = new JPEGMetadata(new ImageTypeSpecifier(rimage), + param, this); + if (metadata.findMarkerSegment + (JFIFMarkerSegment.class, true) != null) { + cs = rimage.getColorModel().getColorSpace(); + if (JPEG.isNonStandardICC(cs)) { + iccProfile = ((ICC_ColorSpace) cs).getProfile(); + } + } + + inCsType = getSrcCSType(rimage); + outCsType = getDefaultDestCSType(rimage); + } + // else no dest, no metadata, not an image, + // so no special headers, no color conversion + } else { // no dest type, but there is metadata + checkSOFBands(sof, numBandsUsed); + if (fullImage) { // no dest, metadata, image + // Check that the metadata and the image match + + ImageTypeSpecifier inputType = + new ImageTypeSpecifier(rimage); + + inCsType = getSrcCSType(rimage); + + if (cm != null) { + boolean alpha = cm.hasAlpha(); + switch (cs.getType()) { + case ColorSpace.TYPE_GRAY: + if (!alpha) { + outCsType = JPEG.JCS_GRAYSCALE; + } else { + if (jfif != null) { + ignoreJFIF = true; + warningOccurred + (WARNING_IMAGE_METADATA_JFIF_MISMATCH); + } + // out colorspace remains unknown + } + if ((adobe != null) + && (adobe.transform != JPEG.ADOBE_UNKNOWN)) { + newAdobeTransform = JPEG.ADOBE_UNKNOWN; + warningOccurred + (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); + } + break; + case ColorSpace.TYPE_RGB: + if (!alpha) { + if (jfif != null) { + outCsType = JPEG.JCS_YCbCr; + if (JPEG.isNonStandardICC(cs) + || ((cs instanceof ICC_ColorSpace) + && (jfif.iccSegment != null))) { + iccProfile = + ((ICC_ColorSpace) cs).getProfile(); + } + } else if (adobe != null) { + switch (adobe.transform) { + case JPEG.ADOBE_UNKNOWN: + outCsType = JPEG.JCS_RGB; + break; + case JPEG.ADOBE_YCC: + outCsType = JPEG.JCS_YCbCr; + break; + default: + warningOccurred + (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); + newAdobeTransform = JPEG.ADOBE_UNKNOWN; + outCsType = JPEG.JCS_RGB; + break; + } + } else { + // consult the ids + int outCS = sof.getIDencodedCSType(); + // if they don't resolve it, + // consult the sampling factors + if (outCS != JPEG.JCS_UNKNOWN) { + outCsType = outCS; + } else { + boolean subsampled = + isSubsampled(sof.componentSpecs); + if (subsampled) { + outCsType = JPEG.JCS_YCbCr; + } else { + outCsType = JPEG.JCS_RGB; + } + } + } + } else { // RGBA + if (jfif != null) { + ignoreJFIF = true; + warningOccurred + (WARNING_IMAGE_METADATA_JFIF_MISMATCH); + } + if (adobe != null) { + if (adobe.transform + != JPEG.ADOBE_UNKNOWN) { + newAdobeTransform = JPEG.ADOBE_UNKNOWN; + warningOccurred + (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); + } + outCsType = JPEG.JCS_RGBA; + } else { + // consult the ids + int outCS = sof.getIDencodedCSType(); + // if they don't resolve it, + // consult the sampling factors + if (outCS != JPEG.JCS_UNKNOWN) { + outCsType = outCS; + } else { + boolean subsampled = + isSubsampled(sof.componentSpecs); + outCsType = subsampled ? + JPEG.JCS_YCbCrA : JPEG.JCS_RGBA; + } + } + } + break; + case ColorSpace.TYPE_3CLR: + if (cs == JPEG.JCS.getYCC()) { + if (!alpha) { + if (jfif != null) { + convertTosRGB = true; + convertOp = + new ColorConvertOp(cs, + JPEG.JCS.sRGB, + null); + outCsType = JPEG.JCS_YCbCr; + } else if (adobe != null) { + if (adobe.transform + != JPEG.ADOBE_YCC) { + newAdobeTransform = JPEG.ADOBE_YCC; + warningOccurred + (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); + } + outCsType = JPEG.JCS_YCC; + } else { + outCsType = JPEG.JCS_YCC; + } + } else { // PhotoYCCA + if (jfif != null) { + ignoreJFIF = true; + warningOccurred + (WARNING_IMAGE_METADATA_JFIF_MISMATCH); + } else if (adobe != null) { + if (adobe.transform + != JPEG.ADOBE_UNKNOWN) { + newAdobeTransform + = JPEG.ADOBE_UNKNOWN; + warningOccurred + (WARNING_IMAGE_METADATA_ADOBE_MISMATCH); + } + } + outCsType = JPEG.JCS_YCCA; + } + } + } + } + } // else no dest, metadata, not an image. Defaults ok + } + } + + boolean metadataProgressive = false; + int [] scans = null; + + if (metadata != null) { + if (sof == null) { + sof = (SOFMarkerSegment) metadata.findMarkerSegment + (SOFMarkerSegment.class, true); + } + if ((sof != null) && (sof.tag == JPEG.SOF2)) { + metadataProgressive = true; + if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) { + scans = collectScans(metadata, sof); // Might still be null + } else { + numScans = 0; + } + } + if (jfif == null) { + jfif = (JFIFMarkerSegment) metadata.findMarkerSegment + (JFIFMarkerSegment.class, true); + } + } + + thumbnails = image.getThumbnails(); + int numThumbs = image.getNumThumbnails(); + forceJFIF = false; + // determine if thumbnails can be written + // If we are going to add a default JFIF marker segment, + // then thumbnails can be written + if (!writeDefaultJFIF) { + // If there is no metadata, then we can't write thumbnails + if (metadata == null) { + thumbnails = null; + if (numThumbs != 0) { + warningOccurred(WARNING_IGNORING_THUMBS); + } + } else { + // There is metadata + // If we are writing a raster or subbands, + // then the user must specify JFIF on the metadata + if (fullImage == false) { + if (jfif == null) { + thumbnails = null; // Or we can't include thumbnails + if (numThumbs != 0) { + warningOccurred(WARNING_IGNORING_THUMBS); + } + } + } else { // It is a full image, and there is metadata + if (jfif == null) { // Not JFIF + // Can it have JFIF? + if ((outCsType == JPEG.JCS_GRAYSCALE) + || (outCsType == JPEG.JCS_YCbCr)) { + if (numThumbs != 0) { + forceJFIF = true; + warningOccurred(WARNING_FORCING_JFIF); + } + } else { // Nope, not JFIF-compatible + thumbnails = null; + if (numThumbs != 0) { + warningOccurred(WARNING_IGNORING_THUMBS); + } + } + } + } + } + } + + // Set up a boolean to indicate whether we need to call back to + // write metadata + boolean haveMetadata = + ((metadata != null) || writeDefaultJFIF || writeAdobe); + + // Now that we have dealt with metadata, finalize our tables set up + + // Are we going to write tables? By default, yes. + boolean writeDQT = true; + boolean writeDHT = true; + + // But if the metadata has no tables, no. + DQTMarkerSegment dqt = null; + DHTMarkerSegment dht = null; + + int restartInterval = 0; + + if (metadata != null) { + dqt = (DQTMarkerSegment) metadata.findMarkerSegment + (DQTMarkerSegment.class, true); + dht = (DHTMarkerSegment) metadata.findMarkerSegment + (DHTMarkerSegment.class, true); + DRIMarkerSegment dri = + (DRIMarkerSegment) metadata.findMarkerSegment + (DRIMarkerSegment.class, true); + if (dri != null) { + restartInterval = dri.restartInterval; + } + + if (dqt == null) { + writeDQT = false; + } + if (dht == null) { + writeDHT = false; // Ignored if optimizeHuffman is true + } + } + + // Whether we write tables or not, we need to figure out which ones + // to use + if (qTables == null) { // Get them from metadata, or use defaults + if (dqt != null) { + qTables = collectQTablesFromMetadata(metadata); + } else if (streamQTables != null) { + qTables = streamQTables; + } else if ((jparam != null) && (jparam.areTablesSet())) { + qTables = jparam.getQTables(); + } else { + qTables = JPEG.getDefaultQTables(); + } + + } + + // If we are optimizing, we don't want any tables. + if (optimizeHuffman == false) { + // If they were for progressive scans, we can't use them. + if ((dht != null) && (metadataProgressive == false)) { + DCHuffmanTables = collectHTablesFromMetadata(metadata, true); + ACHuffmanTables = collectHTablesFromMetadata(metadata, false); + } else if (streamDCHuffmanTables != null) { + DCHuffmanTables = streamDCHuffmanTables; + ACHuffmanTables = streamACHuffmanTables; + } else if ((jparam != null) && (jparam.areTablesSet())) { + DCHuffmanTables = jparam.getDCHuffmanTables(); + ACHuffmanTables = jparam.getACHuffmanTables(); + } else { + DCHuffmanTables = JPEG.getDefaultHuffmanTables(true); + ACHuffmanTables = JPEG.getDefaultHuffmanTables(false); + } + } + + // By default, ids are 1 - N, no subsampling + int [] componentIds = new int[numBandsUsed]; + int [] HsamplingFactors = new int[numBandsUsed]; + int [] VsamplingFactors = new int[numBandsUsed]; + int [] QtableSelectors = new int[numBandsUsed]; + for (int i = 0; i < numBandsUsed; i++) { + componentIds[i] = i+1; // JFIF compatible + HsamplingFactors[i] = 1; + VsamplingFactors[i] = 1; + QtableSelectors[i] = 0; + } + + // Now override them with the contents of sof, if there is one, + if (sof != null) { + for (int i = 0; i < numBandsUsed; i++) { + if (forceJFIF == false) { // else use JFIF-compatible default + componentIds[i] = sof.componentSpecs[i].componentId; + } + HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor; + VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor; + QtableSelectors[i] = sof.componentSpecs[i].QtableSelector; + } + } + + sourceXOffset += gridX; + sourceWidth -= gridX; + sourceYOffset += gridY; + sourceHeight -= gridY; + + int destWidth = (sourceWidth + periodX - 1)/periodX; + int destHeight = (sourceHeight + periodY - 1)/periodY; + + // Create an appropriate 1-line databuffer for writing + int lineSize = sourceWidth*numBandsUsed; + + DataBufferByte buffer = new DataBufferByte(lineSize); + + // Create a raster from that + int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1]; + + raster = Raster.createInterleavedRaster(buffer, + sourceWidth, 1, + lineSize, + numBandsUsed, + bandOffs, + null); + + // Call the writer, who will call back for every scanline + + processImageStarted(currentImage); + + boolean aborted = false; + + if (debug) { + System.out.println("inCsType: " + inCsType); + System.out.println("outCsType: " + outCsType); + } + + // Note that getData disables acceleration on buffer, but it is + // just a 1-line intermediate data transfer buffer that does not + // affect the acceleration of the source image. + aborted = writeImage(structPointer, + buffer.getData(), + inCsType, outCsType, + numBandsUsed, + bandSizes, + sourceWidth, + destWidth, destHeight, + periodX, periodY, + qTables, + writeDQT, + DCHuffmanTables, + ACHuffmanTables, + writeDHT, + optimizeHuffman, + (progressiveMode + != ImageWriteParam.MODE_DISABLED), + numScans, + scans, + componentIds, + HsamplingFactors, + VsamplingFactors, + QtableSelectors, + haveMetadata, + restartInterval); + + cbLock.lock(); + try { + if (aborted) { + processWriteAborted(); + } else { + processImageComplete(); + } + + ios.flush(); + } finally { + cbLock.unlock(); + } + currentImage++; // After a successful write + } + + public void prepareWriteSequence(IIOMetadata streamMetadata) + throws IOException { + setThreadLock(); + try { + cbLock.check(); + + prepareWriteSequenceOnThread(streamMetadata); + } finally { + clearThreadLock(); + } + } + + private void prepareWriteSequenceOnThread(IIOMetadata streamMetadata) + throws IOException { + if (ios == null) { + throw new IllegalStateException("Output has not been set!"); + } + + /* + * from jpeg_metadata.html: + * If no stream metadata is supplied to + * ImageWriter.prepareWriteSequence, then no + * tables-only image is written. If stream metadata containing + * no tables is supplied to + * ImageWriter.prepareWriteSequence, then a tables-only + * image containing default visually lossless tables is written. + */ + if (streamMetadata != null) { + if (streamMetadata instanceof JPEGMetadata) { + // write a complete tables-only image at the beginning of + // the stream. + JPEGMetadata jmeta = (JPEGMetadata) streamMetadata; + if (jmeta.isStream == false) { + throw new IllegalArgumentException + ("Invalid stream metadata object."); + } + // Check that we are + // at the beginning of the stream, or can go there, and haven't + // written out the metadata already. + if (currentImage != 0) { + throw new IIOException + ("JPEG Stream metadata must precede all images"); + } + if (sequencePrepared == true) { + throw new IIOException("Stream metadata already written!"); + } + + // Set the tables + // If the metadata has no tables, use default tables. + streamQTables = collectQTablesFromMetadata(jmeta); + if (debug) { + System.out.println("after collecting from stream metadata, " + + "streamQTables.length is " + + streamQTables.length); + } + if (streamQTables == null) { + streamQTables = JPEG.getDefaultQTables(); + } + streamDCHuffmanTables = + collectHTablesFromMetadata(jmeta, true); + if (streamDCHuffmanTables == null) { + streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true); + } + streamACHuffmanTables = + collectHTablesFromMetadata(jmeta, false); + if (streamACHuffmanTables == null) { + streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false); + } + + // Now write them out + writeTables(structPointer, + streamQTables, + streamDCHuffmanTables, + streamACHuffmanTables); + } else { + throw new IIOException("Stream metadata must be JPEG metadata"); + } + } + sequencePrepared = true; + } + + public void writeToSequence(IIOImage image, ImageWriteParam param) + throws IOException { + setThreadLock(); + try { + cbLock.check(); + + if (sequencePrepared == false) { + throw new IllegalStateException("sequencePrepared not called!"); + } + // In the case of JPEG this does nothing different from write + write(null, image, param); + } finally { + clearThreadLock(); + } + } + + public void endWriteSequence() throws IOException { + setThreadLock(); + try { + cbLock.check(); + + if (sequencePrepared == false) { + throw new IllegalStateException("sequencePrepared not called!"); + } + sequencePrepared = false; + } finally { + clearThreadLock(); + } + } + + public synchronized void abort() { + setThreadLock(); + try { + /** + * NB: we do not check the call back lock here, we allow to abort + * the reader any time. + */ + super.abort(); + abortWrite(structPointer); + } finally { + clearThreadLock(); + } + } + + private void resetInternalState() { + // reset C structures + resetWriter(structPointer); + + // reset local Java structures + srcRas = null; + raster = null; + convertTosRGB = false; + currentImage = 0; + numScans = 0; + metadata = null; + } + + public void reset() { + setThreadLock(); + try { + cbLock.check(); + + super.reset(); + } finally { + clearThreadLock(); + } + } + + public void dispose() { + setThreadLock(); + try { + cbLock.check(); + + if (structPointer != 0) { + disposerRecord.dispose(); + structPointer = 0; + } + } finally { + clearThreadLock(); + } + } + + ////////// End of public API + + ///////// Package-access API + + /** + * Called by the native code or other classes to signal a warning. + * The code is used to lookup a localized message to be used when + * sending warnings to listeners. + */ + void warningOccurred(int code) { + cbLock.lock(); + try { + if ((code < 0) || (code > MAX_WARNING)){ + throw new InternalError("Invalid warning index"); + } + processWarningOccurred + (currentImage, + "com.sun.imageio.plugins.jpeg.JPEGImageWriterResources", + Integer.toString(code)); + } finally { + cbLock.unlock(); + } + } + + /** + * The library has it's own error facility that emits warning messages. + * This routine is called by the native code when it has already + * formatted a string for output. + * XXX For truly complete localization of all warning messages, + * the sun_jpeg_output_message routine in the native code should + * send only the codes and parameters to a method here in Java, + * which will then format and send the warnings, using localized + * strings. This method will have to deal with all the parameters + * and formats (%u with possibly large numbers, %02d, %02x, etc.) + * that actually occur in the JPEG library. For now, this prevents + * library warnings from being printed to stderr. + */ + void warningWithMessage(String msg) { + cbLock.lock(); + try { + processWarningOccurred(currentImage, msg); + } finally { + cbLock.unlock(); + } + } + + void thumbnailStarted(int thumbnailIndex) { + cbLock.lock(); + try { + processThumbnailStarted(currentImage, thumbnailIndex); + } finally { + cbLock.unlock(); + } + } + + // Provide access to protected superclass method + void thumbnailProgress(float percentageDone) { + cbLock.lock(); + try { + processThumbnailProgress(percentageDone); + } finally { + cbLock.unlock(); + } + } + + // Provide access to protected superclass method + void thumbnailComplete() { + cbLock.lock(); + try { + processThumbnailComplete(); + } finally { + cbLock.unlock(); + } + } + + ///////// End of Package-access API + + ///////// Private methods + + ///////// Metadata handling + + private void checkSOFBands(SOFMarkerSegment sof, int numBandsUsed) + throws IIOException { + // Does the metadata frame header, if any, match numBandsUsed? + if (sof != null) { + if (sof.componentSpecs.length != numBandsUsed) { + throw new IIOException + ("Metadata components != number of destination bands"); + } + } + } + + private void checkJFIF(JFIFMarkerSegment jfif, + ImageTypeSpecifier type, + boolean input) { + if (jfif != null) { + if (!JPEG.isJFIFcompliant(type, input)) { + ignoreJFIF = true; // type overrides metadata + warningOccurred(input + ? WARNING_IMAGE_METADATA_JFIF_MISMATCH + : WARNING_DEST_METADATA_JFIF_MISMATCH); + } + } + } + + private void checkAdobe(AdobeMarkerSegment adobe, + ImageTypeSpecifier type, + boolean input) { + if (adobe != null) { + int rightTransform = JPEG.transformForType(type, input); + if (adobe.transform != rightTransform) { + warningOccurred(input + ? WARNING_IMAGE_METADATA_ADOBE_MISMATCH + : WARNING_DEST_METADATA_ADOBE_MISMATCH); + if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) { + ignoreAdobe = true; + } else { + newAdobeTransform = rightTransform; + } + } + } + } + + /** + * Collect all the scan info from the given metadata, and + * organize it into the scan info array required by the + * IJG libray. It is much simpler to parse out this + * data in Java and then just copy the data in C. + */ + private int [] collectScans(JPEGMetadata metadata, + SOFMarkerSegment sof) { + List segments = new ArrayList(); + int SCAN_SIZE = 9; + int MAX_COMPS_PER_SCAN = 4; + for (Iterator iter = metadata.markerSequence.iterator(); + iter.hasNext();) { + MarkerSegment seg = (MarkerSegment) iter.next(); + if (seg instanceof SOSMarkerSegment) { + segments.add(seg); + } + } + int [] retval = null; + numScans = 0; + if (!segments.isEmpty()) { + numScans = segments.size(); + retval = new int [numScans*SCAN_SIZE]; + int index = 0; + for (int i = 0; i < numScans; i++) { + SOSMarkerSegment sos = (SOSMarkerSegment) segments.get(i); + retval[index++] = sos.componentSpecs.length; // num comps + for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) { + if (j < sos.componentSpecs.length) { + int compSel = sos.componentSpecs[j].componentSelector; + for (int k = 0; k < sof.componentSpecs.length; k++) { + if (compSel == sof.componentSpecs[k].componentId) { + retval[index++] = k; + break; // out of for over sof comps + } + } + } else { + retval[index++] = 0; + } + } + retval[index++] = sos.startSpectralSelection; + retval[index++] = sos.endSpectralSelection; + retval[index++] = sos.approxHigh; + retval[index++] = sos.approxLow; + } + } + return retval; + } + + /** + * Finds all DQT marker segments and returns all the q + * tables as a single array of JPEGQTables. + */ + private JPEGQTable [] collectQTablesFromMetadata + (JPEGMetadata metadata) { + ArrayList tables = new ArrayList(); + Iterator iter = metadata.markerSequence.iterator(); + while (iter.hasNext()) { + MarkerSegment seg = (MarkerSegment) iter.next(); + if (seg instanceof DQTMarkerSegment) { + DQTMarkerSegment dqt = + (DQTMarkerSegment) seg; + tables.addAll(dqt.tables); + } + } + JPEGQTable [] retval = null; + if (tables.size() != 0) { + retval = new JPEGQTable[tables.size()]; + for (int i = 0; i < retval.length; i++) { + retval[i] = + new JPEGQTable(((DQTMarkerSegment.Qtable)tables.get(i)).data); + } + } + return retval; + } + + /** + * Finds all DHT marker segments and returns all the q + * tables as a single array of JPEGQTables. The metadata + * must not be for a progressive image, or an exception + * will be thrown when two Huffman tables with the same + * table id are encountered. + */ + private JPEGHuffmanTable[] collectHTablesFromMetadata + (JPEGMetadata metadata, boolean wantDC) throws IIOException { + ArrayList tables = new ArrayList(); + Iterator iter = metadata.markerSequence.iterator(); + while (iter.hasNext()) { + MarkerSegment seg = (MarkerSegment) iter.next(); + if (seg instanceof DHTMarkerSegment) { + DHTMarkerSegment dht = + (DHTMarkerSegment) seg; + for (int i = 0; i < dht.tables.size(); i++) { + DHTMarkerSegment.Htable htable = + (DHTMarkerSegment.Htable) dht.tables.get(i); + if (htable.tableClass == (wantDC ? 0 : 1)) { + tables.add(htable); + } + } + } + } + JPEGHuffmanTable [] retval = null; + if (tables.size() != 0) { + DHTMarkerSegment.Htable [] htables = + new DHTMarkerSegment.Htable[tables.size()]; + tables.toArray(htables); + retval = new JPEGHuffmanTable[tables.size()]; + for (int i = 0; i < retval.length; i++) { + retval[i] = null; + for (int j = 0; j < tables.size(); j++) { + if (htables[j].tableID == i) { + if (retval[i] != null) { + throw new IIOException("Metadata has duplicate Htables!"); + } + retval[i] = new JPEGHuffmanTable(htables[j].numCodes, + htables[j].values); + } + } + } + } + + return retval; + } + + /////////// End of metadata handling + + ////////////// ColorSpace conversion + + private int getSrcCSType(ImageTypeSpecifier type) { + return getSrcCSType(type.getColorModel()); + } + + private int getSrcCSType(RenderedImage rimage) { + return getSrcCSType(rimage.getColorModel()); + } + + private int getSrcCSType(ColorModel cm) { + int retval = JPEG.JCS_UNKNOWN; + if (cm != null) { + boolean alpha = cm.hasAlpha(); + ColorSpace cs = cm.getColorSpace(); + switch (cs.getType()) { + case ColorSpace.TYPE_GRAY: + retval = JPEG.JCS_GRAYSCALE; + break; + case ColorSpace.TYPE_RGB: + if (alpha) { + retval = JPEG.JCS_RGBA; + } else { + retval = JPEG.JCS_RGB; + } + break; + case ColorSpace.TYPE_YCbCr: + if (alpha) { + retval = JPEG.JCS_YCbCrA; + } else { + retval = JPEG.JCS_YCbCr; + } + break; + case ColorSpace.TYPE_3CLR: + if (cs == JPEG.JCS.getYCC()) { + if (alpha) { + retval = JPEG.JCS_YCCA; + } else { + retval = JPEG.JCS_YCC; + } + } + case ColorSpace.TYPE_CMYK: + retval = JPEG.JCS_CMYK; + break; + } + } + return retval; + } + + private int getDestCSType(ImageTypeSpecifier destType) { + ColorModel cm = destType.getColorModel(); + boolean alpha = cm.hasAlpha(); + ColorSpace cs = cm.getColorSpace(); + int retval = JPEG.JCS_UNKNOWN; + switch (cs.getType()) { + case ColorSpace.TYPE_GRAY: + retval = JPEG.JCS_GRAYSCALE; + break; + case ColorSpace.TYPE_RGB: + if (alpha) { + retval = JPEG.JCS_RGBA; + } else { + retval = JPEG.JCS_RGB; + } + break; + case ColorSpace.TYPE_YCbCr: + if (alpha) { + retval = JPEG.JCS_YCbCrA; + } else { + retval = JPEG.JCS_YCbCr; + } + break; + case ColorSpace.TYPE_3CLR: + if (cs == JPEG.JCS.getYCC()) { + if (alpha) { + retval = JPEG.JCS_YCCA; + } else { + retval = JPEG.JCS_YCC; + } + } + case ColorSpace.TYPE_CMYK: + retval = JPEG.JCS_CMYK; + break; + } + return retval; + } + + private int getDefaultDestCSType(ImageTypeSpecifier type) { + return getDefaultDestCSType(type.getColorModel()); + } + + private int getDefaultDestCSType(RenderedImage rimage) { + return getDefaultDestCSType(rimage.getColorModel()); + } + + private int getDefaultDestCSType(ColorModel cm) { + int retval = JPEG.JCS_UNKNOWN; + if (cm != null) { + boolean alpha = cm.hasAlpha(); + ColorSpace cs = cm.getColorSpace(); + switch (cs.getType()) { + case ColorSpace.TYPE_GRAY: + retval = JPEG.JCS_GRAYSCALE; + break; + case ColorSpace.TYPE_RGB: + if (alpha) { + retval = JPEG.JCS_YCbCrA; + } else { + retval = JPEG.JCS_YCbCr; + } + break; + case ColorSpace.TYPE_YCbCr: + if (alpha) { + retval = JPEG.JCS_YCbCrA; + } else { + retval = JPEG.JCS_YCbCr; + } + break; + case ColorSpace.TYPE_3CLR: + if (cs == JPEG.JCS.getYCC()) { + if (alpha) { + retval = JPEG.JCS_YCCA; + } else { + retval = JPEG.JCS_YCC; + } + } + case ColorSpace.TYPE_CMYK: + retval = JPEG.JCS_YCCK; + break; + } + } + return retval; + } + + private boolean isSubsampled(SOFMarkerSegment.ComponentSpec [] specs) { + int hsamp0 = specs[0].HsamplingFactor; + int vsamp0 = specs[0].VsamplingFactor; + for (int i = 1; i < specs.length; i++) { + if ((specs[i].HsamplingFactor != hsamp0) || + (specs[i].HsamplingFactor != hsamp0)) + return true; + } + return false; + } + + ////////////// End of ColorSpace conversion + + ////////////// Native methods and callbacks + + /** Sets up static native structures. */ + private static native void initWriterIDs(Class qTableClass, + Class huffClass); + + /** Sets up per-writer native structure and returns a pointer to it. */ + private native long initJPEGImageWriter(); + + /** Sets up native structures for output stream */ + private native void setDest(long structPointer); + + /** + * Returns true if the write was aborted. + */ + private native boolean writeImage(long structPointer, + byte [] data, + int inCsType, int outCsType, + int numBands, + int [] bandSizes, + int srcWidth, + int destWidth, int destHeight, + int stepX, int stepY, + JPEGQTable [] qtables, + boolean writeDQT, + JPEGHuffmanTable[] DCHuffmanTables, + JPEGHuffmanTable[] ACHuffmanTables, + boolean writeDHT, + boolean optimizeHuffman, + boolean progressive, + int numScans, + int [] scans, + int [] componentIds, + int [] HsamplingFactors, + int [] VsamplingFactors, + int [] QtableSelectors, + boolean haveMetadata, + int restartInterval); + + + /** + * Writes the metadata out when called by the native code, + * which will have already written the header to the stream + * and established the library state. This is simpler than + * breaking the write call in two. + */ + private void writeMetadata() throws IOException { + if (metadata == null) { + if (writeDefaultJFIF) { + JFIFMarkerSegment.writeDefaultJFIF(ios, + thumbnails, + iccProfile, + this); + } + if (writeAdobe) { + AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform); + } + } else { + metadata.writeToStream(ios, + ignoreJFIF, + forceJFIF, + thumbnails, + iccProfile, + ignoreAdobe, + newAdobeTransform, + this); + } + } + + /** + * Write out a tables-only image to the stream. + */ + private native void writeTables(long structPointer, + JPEGQTable [] qtables, + JPEGHuffmanTable[] DCHuffmanTables, + JPEGHuffmanTable[] ACHuffmanTables); + + /** + * Put the scanline y of the source ROI view Raster into the + * 1-line Raster for writing. This handles ROI and band + * rearrangements, and expands indexed images. Subsampling is + * done in the native code. + * This is called by the native code. + */ + private void grabPixels(int y) { + + Raster sourceLine = null; + if (indexed) { + sourceLine = srcRas.createChild(sourceXOffset, + sourceYOffset+y, + sourceWidth, 1, + 0, 0, + new int [] {0}); + // If the image has BITMASK transparency, we need to make sure + // it gets converted to 32-bit ARGB, because the JPEG encoder + // relies upon the full 8-bit alpha channel. + boolean forceARGB = + (indexCM.getTransparency() != Transparency.OPAQUE); + BufferedImage temp = indexCM.convertToIntDiscrete(sourceLine, + forceARGB); + sourceLine = temp.getRaster(); + } else { + sourceLine = srcRas.createChild(sourceXOffset, + sourceYOffset+y, + sourceWidth, 1, + 0, 0, + srcBands); + } + if (convertTosRGB) { + if (debug) { + System.out.println("Converting to sRGB"); + } + // The first time through, converted is null, so + // a new raster is allocated. It is then reused + // on subsequent lines. + converted = convertOp.filter(sourceLine, converted); + sourceLine = converted; + } + if (isAlphaPremultiplied) { + WritableRaster wr = sourceLine.createCompatibleWritableRaster(); + int[] data = null; + data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(), + sourceLine.getWidth(), sourceLine.getHeight(), + data); + wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(), + sourceLine.getWidth(), sourceLine.getHeight(), + data); + srcCM.coerceData(wr, false); + sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(), + wr.getWidth(), wr.getHeight(), + 0, 0, + srcBands); + } + raster.setRect(sourceLine); + if ((y > 7) && (y%8 == 0)) { // Every 8 scanlines + cbLock.lock(); + try { + processImageProgress((float) y / (float) sourceHeight * 100.0F); + } finally { + cbLock.unlock(); + } + } + } + + /** Aborts the current write in the native code */ + private native void abortWrite(long structPointer); + + /** Resets native structures */ + private native void resetWriter(long structPointer); + + /** Releases native structures */ + private static native void disposeWriter(long structPointer); + + private static class JPEGWriterDisposerRecord implements DisposerRecord { + private long pData; + + public JPEGWriterDisposerRecord(long pData) { + this.pData = pData; + } + + public synchronized void dispose() { + if (pData != 0) { + disposeWriter(pData); + pData = 0; + } + } + } + + /** + * This method is called from native code in order to write encoder + * output to the destination. + * + * We block any attempt to change the writer state during this + * method, in order to prevent a corruption of the native encoder + * state. + */ + private void writeOutputData(byte[] data, int offset, int len) + throws IOException + { + cbLock.lock(); + try { + ios.write(data, offset, len); + } finally { + cbLock.unlock(); + } + } + + private Thread theThread = null; + private int theLockCount = 0; + + private synchronized void setThreadLock() { + Thread currThread = Thread.currentThread(); + if (theThread != null) { + if (theThread != currThread) { + // it looks like that this reader instance is used + // by multiple threads. + throw new IllegalStateException("Attempt to use instance of " + + this + " locked on thread " + + theThread + " from thread " + + currThread); + } else { + theLockCount ++; + } + } else { + theThread = currThread; + theLockCount = 1; + } + } + + private synchronized void clearThreadLock() { + Thread currThread = Thread.currentThread(); + if (theThread == null || theThread != currThread) { + throw new IllegalStateException("Attempt to clear thread lock form wrong thread. " + + "Locked thread: " + theThread + + "; current thread: " + currThread); + } + theLockCount --; + if (theLockCount == 0) { + theThread = null; + } + } + + private CallBackLock cbLock = new CallBackLock(); + + private static class CallBackLock { + + private State lockState; + + CallBackLock() { + lockState = State.Unlocked; + } + + void check() { + if (lockState != State.Unlocked) { + throw new IllegalStateException("Access to the writer is not allowed"); + } + } + + private void lock() { + lockState = State.Locked; + } + + private void unlock() { + lockState = State.Unlocked; + } + + private static enum State { + Unlocked, + Locked + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriterResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriterResources.java new file mode 100644 index 000000000..3db20b3d3 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriterResources.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import java.util.ListResourceBundle; + +public class JPEGImageWriterResources extends ListResourceBundle { + + public JPEGImageWriterResources() {} + + protected Object[][] getContents() { + return new Object[][] { + + {Integer.toString(JPEGImageWriter.WARNING_DEST_IGNORED), + "Only Rasters or band subsets may be written with a destination type. " + + "Destination type ignored."}, + {Integer.toString(JPEGImageWriter.WARNING_STREAM_METADATA_IGNORED), + "Stream metadata ignored on write"}, + {Integer.toString(JPEGImageWriter.WARNING_DEST_METADATA_COMP_MISMATCH), + "Metadata component ids incompatible with destination type. " + + "Metadata modified."}, + {Integer.toString(JPEGImageWriter.WARNING_DEST_METADATA_JFIF_MISMATCH), + "Metadata JFIF settings incompatible with destination type. " + + "Metadata modified."}, + {Integer.toString(JPEGImageWriter.WARNING_DEST_METADATA_ADOBE_MISMATCH), + "Metadata Adobe settings incompatible with destination type. " + + "Metadata modified."}, + {Integer.toString(JPEGImageWriter.WARNING_IMAGE_METADATA_JFIF_MISMATCH), + "Metadata JFIF settings incompatible with image type. " + + "Metadata modified."}, + {Integer.toString(JPEGImageWriter.WARNING_IMAGE_METADATA_ADOBE_MISMATCH), + "Metadata Adobe settings incompatible with image type. " + + "Metadata modified."}, + {Integer.toString(JPEGImageWriter.WARNING_METADATA_NOT_JPEG_FOR_RASTER), + "Metadata must be JPEGMetadata when writing a Raster. " + + "Metadata ignored."}, + {Integer.toString(JPEGImageWriter.WARNING_NO_BANDS_ON_INDEXED), + "Band subset not allowed for an IndexColorModel image. " + + "Band subset ignored."}, + {Integer.toString(JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL), + "Thumbnails must be simple (possibly index) RGB or grayscale. " + + "Incompatible thumbnail ignored."}, + {Integer.toString(JPEGImageWriter.WARNING_IGNORING_THUMBS ), + "Thumbnails ignored for non-JFIF-compatible image."}, + {Integer.toString(JPEGImageWriter.WARNING_FORCING_JFIF ), + "Thumbnails require JFIF marker segment. " + + "Missing node added to metadata."}, + {Integer.toString(JPEGImageWriter.WARNING_THUMB_CLIPPED ), + "Thumbnail clipped."}, + {Integer.toString(JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB ), + "Metadata adjusted (made JFIF-compatible) for thumbnail."}, + {Integer.toString(JPEGImageWriter.WARNING_NO_RGB_THUMB_AS_INDEXED ), + "RGB thumbnail can't be written as indexed. Written as RGB"}, + {Integer.toString(JPEGImageWriter.WARNING_NO_GRAY_THUMB_AS_INDEXED), + "Grayscale thumbnail can't be written as indexed. Written as JPEG"}, + + }; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriterSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriterSpi.java new file mode 100644 index 000000000..9973992f6 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGImageWriterSpi.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2000, 2004, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.spi.IIORegistry; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.ImageWriter; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.IIOException; + +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.SampleModel; +import java.util.Locale; + +public class JPEGImageWriterSpi extends ImageWriterSpi { + + private static String [] readerSpiNames = + {"com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi"}; + + public JPEGImageWriterSpi() { + super(JPEG.vendor, + JPEG.version, + JPEG.names, + JPEG.suffixes, + JPEG.MIMETypes, + "com.sun.imageio.plugins.jpeg.JPEGImageWriter", + new Class[] { ImageOutputStream.class }, + readerSpiNames, + true, + JPEG.nativeStreamMetadataFormatName, + JPEG.nativeStreamMetadataFormatClassName, + null, null, + true, + JPEG.nativeImageMetadataFormatName, + JPEG.nativeImageMetadataFormatClassName, + null, null + ); + } + + public String getDescription(Locale locale) { + return "Standard JPEG Image Writer"; + } + + public boolean isFormatLossless() { + return false; + } + + public boolean canEncodeImage(ImageTypeSpecifier type) { + SampleModel sampleModel = type.getSampleModel(); + + // Find the maximum bit depth across all channels + int[] sampleSize = sampleModel.getSampleSize(); + int bitDepth = sampleSize[0]; + for (int i = 1; i < sampleSize.length; i++) { + if (sampleSize[i] > bitDepth) { + bitDepth = sampleSize[i]; + } + } + + // 4450894: Ensure bitDepth is between 1 and 8 + if (bitDepth < 1 || bitDepth > 8) { + return false; + } + + return true; + } + + public ImageWriter createWriterInstance(Object extension) + throws IIOException { + return new JPEGImageWriter(this); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadata.java new file mode 100644 index 000000000..c7b8fd3d0 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadata.java @@ -0,0 +1,2417 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.plugins.jpeg.JPEGQTable; +import javax.imageio.plugins.jpeg.JPEGHuffmanTable; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.NamedNodeMap; + +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.ListIterator; +import java.io.IOException; +import java.awt.color.ICC_Profile; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ColorSpace; +import java.awt.image.ColorModel; +import java.awt.Point; + +/** + * Metadata for the JPEG plug-in. + */ +public class JPEGMetadata extends IIOMetadata implements Cloneable { + + //////// Private variables + + private static final boolean debug = false; + + /** + * A copy of markerSequence, created the first time the + * markerSequence is modified. This is used by reset + * to restore the original state. + */ + private List resetSequence = null; + + /** + * Set to true when reading a thumbnail stored as + * JPEG. This is used to enforce the prohibition of JFIF thumbnails + * containing any JFIF marker segments, and to ensure generation of + * a correct native subtree during getAsTree. + */ + private boolean inThumb = false; + + /** + * Set by the chroma node construction method to signal the + * presence or absence of an alpha channel to the transparency + * node construction method. Used only when constructing a + * standard metadata tree. + */ + private boolean hasAlpha; + + //////// end of private variables + + /////// Package-access variables + + /** + * All data is a list of MarkerSegment objects. + * When accessing the list, use the tag to identify the particular + * subclass. Any JFIF marker segment must be the first element + * of the list if it is present, and any JFXX or APP2ICC marker + * segments are subordinate to the JFIF marker segment. This + * list is package visible so that the writer can access it. + * @see #MarkerSegment + */ + List markerSequence = new ArrayList(); + + /** + * Indicates whether this object represents stream or image + * metadata. Package-visible so the writer can see it. + */ + final boolean isStream; + + /////// End of package-access variables + + /////// Constructors + + /** + * Constructor containing code shared by other constructors. + */ + JPEGMetadata(boolean isStream, boolean inThumb) { + super(true, // Supports standard format + JPEG.nativeImageMetadataFormatName, // and a native format + JPEG.nativeImageMetadataFormatClassName, + null, null); // No other formats + this.inThumb = inThumb; + // But if we are stream metadata, adjust the variables + this.isStream = isStream; + if (isStream) { + nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName; + nativeMetadataFormatClassName = + JPEG.nativeStreamMetadataFormatClassName; + } + } + + /* + * Constructs a JPEGMetadata object by reading the + * contents of an ImageInputStream. Has package-only + * access. + * + * @param isStream A boolean indicating whether this object will be + * stream or image metadata. + * @param isThumb A boolean indicating whether this metadata object + * is for an image or for a thumbnail stored as JPEG. + * @param iis An ImageInputStream from which to read + * the metadata. + * @param reader The JPEGImageReader calling this + * constructor, to which warnings should be sent. + */ + JPEGMetadata(boolean isStream, + boolean isThumb, + ImageInputStream iis, + JPEGImageReader reader) throws IOException { + this(isStream, isThumb); + + JPEGBuffer buffer = new JPEGBuffer(iis); + + buffer.loadBuf(0); + + // The first three bytes should be FF, SOI, FF + if (((buffer.buf[0] & 0xff) != 0xff) + || ((buffer.buf[1] & 0xff) != JPEG.SOI) + || ((buffer.buf[2] & 0xff) != 0xff)) { + throw new IIOException ("Image format error"); + } + + boolean done = false; + buffer.bufAvail -=2; // Next byte should be the ff before a marker + buffer.bufPtr = 2; + MarkerSegment newGuy = null; + while (!done) { + byte [] buf; + int ptr; + buffer.loadBuf(1); + if (debug) { + System.out.println("top of loop"); + buffer.print(10); + } + buffer.scanForFF(reader); + switch (buffer.buf[buffer.bufPtr] & 0xff) { + case 0: + if (debug) { + System.out.println("Skipping 0"); + } + buffer.bufAvail--; + buffer.bufPtr++; + break; + case JPEG.SOF0: + case JPEG.SOF1: + case JPEG.SOF2: + if (isStream) { + throw new IIOException + ("SOF not permitted in stream metadata"); + } + newGuy = new SOFMarkerSegment(buffer); + break; + case JPEG.DQT: + newGuy = new DQTMarkerSegment(buffer); + break; + case JPEG.DHT: + newGuy = new DHTMarkerSegment(buffer); + break; + case JPEG.DRI: + newGuy = new DRIMarkerSegment(buffer); + break; + case JPEG.APP0: + // Either JFIF, JFXX, or unknown APP0 + buffer.loadBuf(8); // tag, length, id + buf = buffer.buf; + ptr = buffer.bufPtr; + if ((buf[ptr+3] == 'J') + && (buf[ptr+4] == 'F') + && (buf[ptr+5] == 'I') + && (buf[ptr+6] == 'F') + && (buf[ptr+7] == 0)) { + if (inThumb) { + reader.warningOccurred + (JPEGImageReader.WARNING_NO_JFIF_IN_THUMB); + // Leave newGuy null + // Read a dummy to skip the segment + JFIFMarkerSegment dummy = + new JFIFMarkerSegment(buffer); + } else if (isStream) { + throw new IIOException + ("JFIF not permitted in stream metadata"); + } else if (markerSequence.isEmpty() == false) { + throw new IIOException + ("JFIF APP0 must be first marker after SOI"); + } else { + newGuy = new JFIFMarkerSegment(buffer); + } + } else if ((buf[ptr+3] == 'J') + && (buf[ptr+4] == 'F') + && (buf[ptr+5] == 'X') + && (buf[ptr+6] == 'X') + && (buf[ptr+7] == 0)) { + if (isStream) { + throw new IIOException + ("JFXX not permitted in stream metadata"); + } + if (inThumb) { + throw new IIOException + ("JFXX markers not allowed in JFIF JPEG thumbnail"); + } + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) findMarkerSegment + (JFIFMarkerSegment.class, true); + if (jfif == null) { + throw new IIOException + ("JFXX encountered without prior JFIF!"); + } + jfif.addJFXX(buffer, reader); + // newGuy remains null + } else { + newGuy = new MarkerSegment(buffer); + newGuy.loadData(buffer); + } + break; + case JPEG.APP2: + // Either an ICC profile or unknown APP2 + buffer.loadBuf(15); // tag, length, id + if ((buffer.buf[buffer.bufPtr+3] == 'I') + && (buffer.buf[buffer.bufPtr+4] == 'C') + && (buffer.buf[buffer.bufPtr+5] == 'C') + && (buffer.buf[buffer.bufPtr+6] == '_') + && (buffer.buf[buffer.bufPtr+7] == 'P') + && (buffer.buf[buffer.bufPtr+8] == 'R') + && (buffer.buf[buffer.bufPtr+9] == 'O') + && (buffer.buf[buffer.bufPtr+10] == 'F') + && (buffer.buf[buffer.bufPtr+11] == 'I') + && (buffer.buf[buffer.bufPtr+12] == 'L') + && (buffer.buf[buffer.bufPtr+13] == 'E') + && (buffer.buf[buffer.bufPtr+14] == 0) + ) { + if (isStream) { + throw new IIOException + ("ICC profiles not permitted in stream metadata"); + } + + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) findMarkerSegment + (JFIFMarkerSegment.class, true); + if (jfif == null) { + newGuy = new MarkerSegment(buffer); + newGuy.loadData(buffer); + } else { + jfif.addICC(buffer); + } + // newGuy remains null + } else { + newGuy = new MarkerSegment(buffer); + newGuy.loadData(buffer); + } + break; + case JPEG.APP14: + // Either Adobe or unknown APP14 + buffer.loadBuf(8); // tag, length, id + if ((buffer.buf[buffer.bufPtr+3] == 'A') + && (buffer.buf[buffer.bufPtr+4] == 'd') + && (buffer.buf[buffer.bufPtr+5] == 'o') + && (buffer.buf[buffer.bufPtr+6] == 'b') + && (buffer.buf[buffer.bufPtr+7] == 'e')) { + if (isStream) { + throw new IIOException + ("Adobe APP14 markers not permitted in stream metadata"); + } + newGuy = new AdobeMarkerSegment(buffer); + } else { + newGuy = new MarkerSegment(buffer); + newGuy.loadData(buffer); + } + + break; + case JPEG.COM: + newGuy = new COMMarkerSegment(buffer); + break; + case JPEG.SOS: + if (isStream) { + throw new IIOException + ("SOS not permitted in stream metadata"); + } + newGuy = new SOSMarkerSegment(buffer); + break; + case JPEG.RST0: + case JPEG.RST1: + case JPEG.RST2: + case JPEG.RST3: + case JPEG.RST4: + case JPEG.RST5: + case JPEG.RST6: + case JPEG.RST7: + if (debug) { + System.out.println("Restart Marker"); + } + buffer.bufPtr++; // Just skip it + buffer.bufAvail--; + break; + case JPEG.EOI: + done = true; + buffer.bufPtr++; + buffer.bufAvail--; + break; + default: + newGuy = new MarkerSegment(buffer); + newGuy.loadData(buffer); + newGuy.unknown = true; + break; + } + if (newGuy != null) { + markerSequence.add(newGuy); + if (debug) { + newGuy.print(); + } + newGuy = null; + } + } + + // Now that we've read up to the EOI, we need to push back + // whatever is left in the buffer, so that the next read + // in the native code will work. + + buffer.pushBack(); + + if (!isConsistent()) { + throw new IIOException("Inconsistent metadata read from stream"); + } + } + + /** + * Constructs a default stream JPEGMetadata object appropriate + * for the given write parameters. + */ + JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer) { + this(true, false); + + JPEGImageWriteParam jparam = null; + + if ((param != null) && (param instanceof JPEGImageWriteParam)) { + jparam = (JPEGImageWriteParam) param; + if (!jparam.areTablesSet()) { + jparam = null; + } + } + if (jparam != null) { + markerSequence.add(new DQTMarkerSegment(jparam.getQTables())); + markerSequence.add + (new DHTMarkerSegment(jparam.getDCHuffmanTables(), + jparam.getACHuffmanTables())); + } else { + // default tables. + markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables())); + markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true), + JPEG.getDefaultHuffmanTables(false))); + } + + // Defensive programming + if (!isConsistent()) { + throw new InternalError("Default stream metadata is inconsistent"); + } + } + + /** + * Constructs a default image JPEGMetadata object appropriate + * for the given image type and write parameters. + */ + JPEGMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param, + JPEGImageWriter writer) { + this(false, false); + + boolean wantJFIF = true; + boolean wantAdobe = false; + int transform = JPEG.ADOBE_UNKNOWN; + boolean willSubsample = true; + boolean wantICC = false; + boolean wantProg = false; + boolean wantOptimized = false; + boolean wantExtended = false; + boolean wantQTables = true; + boolean wantHTables = true; + float quality = JPEG.DEFAULT_QUALITY; + byte[] componentIDs = { 1, 2, 3, 4}; + int numComponents = 0; + + ImageTypeSpecifier destType = null; + + if (param != null) { + destType = param.getDestinationType(); + if (destType != null) { + if (imageType != null) { + // Ignore the destination type. + writer.warningOccurred + (JPEGImageWriter.WARNING_DEST_IGNORED); + destType = null; + } + } + // The only progressive mode that makes sense here is MODE_DEFAULT + if (param.canWriteProgressive()) { + // the param may not be one of ours, so it may return false. + // If so, the following would throw an exception + if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) { + wantProg = true; + wantOptimized = true; + wantHTables = false; + } + } + + if (param instanceof JPEGImageWriteParam) { + JPEGImageWriteParam jparam = (JPEGImageWriteParam) param; + if (jparam.areTablesSet()) { + wantQTables = false; // If the param has them, metadata shouldn't + wantHTables = false; + if ((jparam.getDCHuffmanTables().length > 2) + || (jparam.getACHuffmanTables().length > 2)) { + wantExtended = true; + } + } + // Progressive forces optimized, regardless of param setting + // so consult the param re optimized only if not progressive + if (!wantProg) { + wantOptimized = jparam.getOptimizeHuffmanTables(); + if (wantOptimized) { + wantHTables = false; + } + } + } + + // compression quality should determine the q tables. Note that this + // will be ignored if we already decided not to create any. + // Again, the param may not be one of ours, so we must check that it + // supports compression settings + if (param.canWriteCompressed()) { + if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) { + quality = param.getCompressionQuality(); + } + } + } + + // We are done with the param, now for the image types + + ColorSpace cs = null; + if (destType != null) { + ColorModel cm = destType.getColorModel(); + numComponents = cm.getNumComponents(); + boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents); + boolean hasAlpha = cm.hasAlpha(); + cs = cm.getColorSpace(); + int type = cs.getType(); + switch(type) { + case ColorSpace.TYPE_GRAY: + willSubsample = false; + if (hasExtraComponents) { // e.g. alpha + wantJFIF = false; + } + break; + case ColorSpace.TYPE_3CLR: + if (cs == JPEG.JCS.getYCC()) { + wantJFIF = false; + componentIDs[0] = (byte) 'Y'; + componentIDs[1] = (byte) 'C'; + componentIDs[2] = (byte) 'c'; + if (hasAlpha) { + componentIDs[3] = (byte) 'A'; + } + } + break; + case ColorSpace.TYPE_YCbCr: + if (hasExtraComponents) { // e.g. K or alpha + wantJFIF = false; + if (!hasAlpha) { // Not alpha, so must be K + wantAdobe = true; + transform = JPEG.ADOBE_YCCK; + } + } + break; + case ColorSpace.TYPE_RGB: // with or without alpha + wantJFIF = false; + wantAdobe = true; + willSubsample = false; + componentIDs[0] = (byte) 'R'; + componentIDs[1] = (byte) 'G'; + componentIDs[2] = (byte) 'B'; + if (hasAlpha) { + componentIDs[3] = (byte) 'A'; + } + break; + default: + // Everything else is not subsampled, gets no special marker, + // and component ids are 1 - N + wantJFIF = false; + willSubsample = false; + } + } else if (imageType != null) { + ColorModel cm = imageType.getColorModel(); + numComponents = cm.getNumComponents(); + boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents); + boolean hasAlpha = cm.hasAlpha(); + cs = cm.getColorSpace(); + int type = cs.getType(); + switch(type) { + case ColorSpace.TYPE_GRAY: + willSubsample = false; + if (hasExtraComponents) { // e.g. alpha + wantJFIF = false; + } + break; + case ColorSpace.TYPE_RGB: // with or without alpha + // without alpha we just accept the JFIF defaults + if (hasAlpha) { + wantJFIF = false; + } + break; + case ColorSpace.TYPE_3CLR: + wantJFIF = false; + willSubsample = false; + if (cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))) { + willSubsample = true; + wantAdobe = true; + componentIDs[0] = (byte) 'Y'; + componentIDs[1] = (byte) 'C'; + componentIDs[2] = (byte) 'c'; + if (hasAlpha) { + componentIDs[3] = (byte) 'A'; + } + } + break; + case ColorSpace.TYPE_YCbCr: + if (hasExtraComponents) { // e.g. K or alpha + wantJFIF = false; + if (!hasAlpha) { // then it must be K + wantAdobe = true; + transform = JPEG.ADOBE_YCCK; + } + } + break; + case ColorSpace.TYPE_CMYK: + wantJFIF = false; + wantAdobe = true; + transform = JPEG.ADOBE_YCCK; + break; + + default: + // Everything else is not subsampled, gets no special marker, + // and component ids are 0 - N + wantJFIF = false; + willSubsample = false; + } + + } + + // do we want an ICC profile? + if (wantJFIF && JPEG.isNonStandardICC(cs)) { + wantICC = true; + } + + // Now step through the markers, consulting our variables. + if (wantJFIF) { + JFIFMarkerSegment jfif = new JFIFMarkerSegment(); + markerSequence.add(jfif); + if (wantICC) { + try { + jfif.addICC((ICC_ColorSpace)cs); + } catch (IOException e) {} // Can't happen here + } + } + // Adobe + if (wantAdobe) { + markerSequence.add(new AdobeMarkerSegment(transform)); + } + + // dqt + if (wantQTables) { + markerSequence.add(new DQTMarkerSegment(quality, willSubsample)); + } + + // dht + if (wantHTables) { + markerSequence.add(new DHTMarkerSegment(willSubsample)); + } + + // sof + markerSequence.add(new SOFMarkerSegment(wantProg, + wantExtended, + willSubsample, + componentIDs, + numComponents)); + + // sos + if (!wantProg) { // Default progression scans are done in the writer + markerSequence.add(new SOSMarkerSegment(willSubsample, + componentIDs, + numComponents)); + } + + // Defensive programming + if (!isConsistent()) { + throw new InternalError("Default image metadata is inconsistent"); + } + } + + ////// End of constructors + + // Utilities for dealing with the marker sequence. + // The first ones have package access for access from the writer. + + /** + * Returns the first MarkerSegment object in the list + * with the given tag, or null if none is found. + */ + MarkerSegment findMarkerSegment(int tag) { + Iterator iter = markerSequence.iterator(); + while (iter.hasNext()) { + MarkerSegment seg = (MarkerSegment)iter.next(); + if (seg.tag == tag) { + return seg; + } + } + return null; + } + + /** + * Returns the first or last MarkerSegment object in the list + * of the given class, or null if none is found. + */ + MarkerSegment findMarkerSegment(Class cls, boolean first) { + if (first) { + Iterator iter = markerSequence.iterator(); + while (iter.hasNext()) { + MarkerSegment seg = (MarkerSegment)iter.next(); + if (cls.isInstance(seg)) { + return seg; + } + } + } else { + ListIterator iter = markerSequence.listIterator(markerSequence.size()); + while (iter.hasPrevious()) { + MarkerSegment seg = (MarkerSegment)iter.previous(); + if (cls.isInstance(seg)) { + return seg; + } + } + } + return null; + } + + /** + * Returns the index of the first or last MarkerSegment in the list + * of the given class, or -1 if none is found. + */ + private int findMarkerSegmentPosition(Class cls, boolean first) { + if (first) { + ListIterator iter = markerSequence.listIterator(); + for (int i = 0; iter.hasNext(); i++) { + MarkerSegment seg = (MarkerSegment)iter.next(); + if (cls.isInstance(seg)) { + return i; + } + } + } else { + ListIterator iter = markerSequence.listIterator(markerSequence.size()); + for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) { + MarkerSegment seg = (MarkerSegment)iter.previous(); + if (cls.isInstance(seg)) { + return i; + } + } + } + return -1; + } + + private int findLastUnknownMarkerSegmentPosition() { + ListIterator iter = markerSequence.listIterator(markerSequence.size()); + for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) { + MarkerSegment seg = (MarkerSegment)iter.previous(); + if (seg.unknown == true) { + return i; + } + } + return -1; + } + + // Implement Cloneable, but restrict access + + protected Object clone() { + JPEGMetadata newGuy = null; + try { + newGuy = (JPEGMetadata) super.clone(); + } catch (CloneNotSupportedException e) {} // won't happen + if (markerSequence != null) { + newGuy.markerSequence = (List) cloneSequence(); + } + newGuy.resetSequence = null; + return newGuy; + } + + /** + * Returns a deep copy of the current marker sequence. + */ + private List cloneSequence() { + if (markerSequence == null) { + return null; + } + List retval = new ArrayList(markerSequence.size()); + Iterator iter = markerSequence.iterator(); + while(iter.hasNext()) { + MarkerSegment seg = (MarkerSegment)iter.next(); + retval.add(seg.clone()); + } + + return retval; + } + + + // Tree methods + + public Node getAsTree(String formatName) { + if (formatName == null) { + throw new IllegalArgumentException("null formatName!"); + } + if (isStream) { + if (formatName.equals(JPEG.nativeStreamMetadataFormatName)) { + return getNativeTree(); + } + } else { + if (formatName.equals(JPEG.nativeImageMetadataFormatName)) { + return getNativeTree(); + } + if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } + } + throw new IllegalArgumentException("Unsupported format name: " + + formatName); + } + + IIOMetadataNode getNativeTree() { + IIOMetadataNode root; + IIOMetadataNode top; + Iterator iter = markerSequence.iterator(); + if (isStream) { + root = new IIOMetadataNode(JPEG.nativeStreamMetadataFormatName); + top = root; + } else { + IIOMetadataNode sequence = new IIOMetadataNode("markerSequence"); + if (!inThumb) { + root = new IIOMetadataNode(JPEG.nativeImageMetadataFormatName); + IIOMetadataNode header = new IIOMetadataNode("JPEGvariety"); + root.appendChild(header); + JFIFMarkerSegment jfif = (JFIFMarkerSegment) + findMarkerSegment(JFIFMarkerSegment.class, true); + if (jfif != null) { + iter.next(); // JFIF must be first, so this skips it + header.appendChild(jfif.getNativeNode()); + } + root.appendChild(sequence); + } else { + root = sequence; + } + top = sequence; + } + while(iter.hasNext()) { + MarkerSegment seg = (MarkerSegment) iter.next(); + top.appendChild(seg.getNativeNode()); + } + return root; + } + + // Standard tree node methods + + protected IIOMetadataNode getStandardChromaNode() { + hasAlpha = false; // Unless we find otherwise + + // Colorspace type - follow the rules in the spec + // First get the SOF marker segment, if there is one + SOFMarkerSegment sof = (SOFMarkerSegment) + findMarkerSegment(SOFMarkerSegment.class, true); + if (sof == null) { + // No image, so no chroma + return null; + } + + IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); + IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); + chroma.appendChild(csType); + + // get the number of channels + int numChannels = sof.componentSpecs.length; + + IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels"); + chroma.appendChild(numChanNode); + numChanNode.setAttribute("value", Integer.toString(numChannels)); + + // is there a JFIF marker segment? + if (findMarkerSegment(JFIFMarkerSegment.class, true) != null) { + if (numChannels == 1) { + csType.setAttribute("name", "GRAY"); + } else { + csType.setAttribute("name", "YCbCr"); + } + return chroma; + } + + // How about an Adobe marker segment? + AdobeMarkerSegment adobe = + (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true); + if (adobe != null){ + switch (adobe.transform) { + case JPEG.ADOBE_YCCK: + csType.setAttribute("name", "YCCK"); + break; + case JPEG.ADOBE_YCC: + csType.setAttribute("name", "YCbCr"); + break; + case JPEG.ADOBE_UNKNOWN: + if (numChannels == 3) { + csType.setAttribute("name", "RGB"); + } else if (numChannels == 4) { + csType.setAttribute("name", "CMYK"); + } + break; + } + return chroma; + } + + // Neither marker. Check components + if (numChannels < 3) { + csType.setAttribute("name", "GRAY"); + if (numChannels == 2) { + hasAlpha = true; + } + return chroma; + } + + boolean idsAreJFIF = true; + + for (int i = 0; i < sof.componentSpecs.length; i++) { + int id = sof.componentSpecs[i].componentId; + if ((id < 1) || (id >= sof.componentSpecs.length)) { + idsAreJFIF = false; + } + } + + if (idsAreJFIF) { + csType.setAttribute("name", "YCbCr"); + if (numChannels == 4) { + hasAlpha = true; + } + return chroma; + } + + // Check against the letters + if ((sof.componentSpecs[0].componentId == 'R') + && (sof.componentSpecs[1].componentId == 'G') + && (sof.componentSpecs[2].componentId == 'B')){ + + csType.setAttribute("name", "RGB"); + if ((numChannels == 4) + && (sof.componentSpecs[3].componentId == 'A')) { + hasAlpha = true; + } + return chroma; + } + + if ((sof.componentSpecs[0].componentId == 'Y') + && (sof.componentSpecs[1].componentId == 'C') + && (sof.componentSpecs[2].componentId == 'c')){ + + csType.setAttribute("name", "PhotoYCC"); + if ((numChannels == 4) + && (sof.componentSpecs[3].componentId == 'A')) { + hasAlpha = true; + } + return chroma; + } + + // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB + // 4-channel subsampled are YCbCrA, unsubsampled are CMYK + + boolean subsampled = false; + + int hfactor = sof.componentSpecs[0].HsamplingFactor; + int vfactor = sof.componentSpecs[0].VsamplingFactor; + + for (int i = 1; i 1 SOS node", node); + } + firstSOS.updateFromNativeNode(node, false); + } else { + markerSequence.add(new SOSMarkerSegment(node)); + } + } + + private boolean transparencyDone; + + private void mergeStandardTree(Node root) throws IIOInvalidTreeException { + transparencyDone = false; + NodeList children = root.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + String name = node.getNodeName(); + if (name.equals("Chroma")) { + mergeStandardChromaNode(node, children); + } else if (name.equals("Compression")) { + mergeStandardCompressionNode(node); + } else if (name.equals("Data")) { + mergeStandardDataNode(node); + } else if (name.equals("Dimension")) { + mergeStandardDimensionNode(node); + } else if (name.equals("Document")) { + mergeStandardDocumentNode(node); + } else if (name.equals("Text")) { + mergeStandardTextNode(node); + } else if (name.equals("Transparency")) { + mergeStandardTransparencyNode(node); + } else { + throw new IIOInvalidTreeException("Invalid node: " + name, node); + } + } + } + + /* + * In general, it could be possible to convert all non-pixel data to some + * textual form and include it in comments, but then this would create the + * expectation that these comment forms be recognized by the reader, thus + * creating a defacto extension to JPEG metadata capabilities. This is + * probably best avoided, so the following convert only text nodes to + * comments, and lose the keywords as well. + */ + + private void mergeStandardChromaNode(Node node, NodeList siblings) + throws IIOInvalidTreeException { + // ColorSpaceType can change the target colorspace for compression + // This must take any transparency node into account as well, as + // that affects the number of channels (if alpha is present). If + // a transparency node is dealt with here, set a flag to indicate + // this to the transparency processor below. If we discover that + // the nodes are not in order, throw an exception as the tree is + // invalid. + + if (transparencyDone) { + throw new IIOInvalidTreeException + ("Transparency node must follow Chroma node", node); + } + + Node csType = node.getFirstChild(); + if ((csType == null) || !csType.getNodeName().equals("ColorSpaceType")) { + // If there is no ColorSpaceType node, we have nothing to do + return; + } + + String csName = csType.getAttributes().getNamedItem("name").getNodeValue(); + + int numChannels = 0; + boolean wantJFIF = false; + boolean wantAdobe = false; + int transform = 0; + boolean willSubsample = false; + byte [] ids = {1, 2, 3, 4}; // JFIF compatible + if (csName.equals("GRAY")) { + numChannels = 1; + wantJFIF = true; + } else if (csName.equals("YCbCr")) { + numChannels = 3; + wantJFIF = true; + willSubsample = true; + } else if (csName.equals("PhotoYCC")) { + numChannels = 3; + wantAdobe = true; + transform = JPEG.ADOBE_YCC; + ids[0] = (byte) 'Y'; + ids[1] = (byte) 'C'; + ids[2] = (byte) 'c'; + } else if (csName.equals("RGB")) { + numChannels = 3; + wantAdobe = true; + transform = JPEG.ADOBE_UNKNOWN; + ids[0] = (byte) 'R'; + ids[1] = (byte) 'G'; + ids[2] = (byte) 'B'; + } else if ((csName.equals("XYZ")) + || (csName.equals("Lab")) + || (csName.equals("Luv")) + || (csName.equals("YxY")) + || (csName.equals("HSV")) + || (csName.equals("HLS")) + || (csName.equals("CMY")) + || (csName.equals("3CLR"))) { + numChannels = 3; + } else if (csName.equals("YCCK")) { + numChannels = 4; + wantAdobe = true; + transform = JPEG.ADOBE_YCCK; + willSubsample = true; + } else if (csName.equals("CMYK")) { + numChannels = 4; + wantAdobe = true; + transform = JPEG.ADOBE_UNKNOWN; + } else if (csName.equals("4CLR")) { + numChannels = 4; + } else { // We can't handle them, so don't modify any metadata + return; + } + + boolean wantAlpha = false; + for (int i = 0; i < siblings.getLength(); i++) { + Node trans = siblings.item(i); + if (trans.getNodeName().equals("Transparency")) { + wantAlpha = wantAlpha(trans); + break; // out of for + } + } + + if (wantAlpha) { + numChannels++; + wantJFIF = false; + if (ids[0] == (byte) 'R') { + ids[3] = (byte) 'A'; + wantAdobe = false; + } + } + + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true); + AdobeMarkerSegment adobe = + (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true); + SOFMarkerSegment sof = + (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true); + SOSMarkerSegment sos = + (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true); + + // If the metadata specifies progressive, then the number of channels + // must match, so that we can modify all the existing SOS marker segments. + // If they don't match, we don't know what to do with SOS so we can't do + // the merge. We then just return silently. + // An exception would not be appropriate. A warning might, but we have + // nowhere to send it to. + if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive + if ((sof.componentSpecs.length != numChannels) && (sos != null)) { + return; + } + } + + // JFIF header might be removed + if (!wantJFIF && (jfif != null)) { + markerSequence.remove(jfif); + } + + // Now add a JFIF if we do want one, but only if it isn't stream metadata + if (wantJFIF && !isStream) { + markerSequence.add(0, new JFIFMarkerSegment()); + } + + // Adobe header might be removed or the transform modified, if it isn't + // stream metadata + if (wantAdobe) { + if ((adobe == null) && !isStream) { + adobe = new AdobeMarkerSegment(transform); + insertAdobeMarkerSegment(adobe); + } else { + adobe.transform = transform; + } + } else if (adobe != null) { + markerSequence.remove(adobe); + } + + boolean updateQtables = false; + boolean updateHtables = false; + + boolean progressive = false; + + int [] subsampledSelectors = {0, 1, 1, 0 } ; + int [] nonSubsampledSelectors = { 0, 0, 0, 0}; + + int [] newTableSelectors = willSubsample + ? subsampledSelectors + : nonSubsampledSelectors; + + // Keep the old componentSpecs array + SOFMarkerSegment.ComponentSpec [] oldCompSpecs = null; + // SOF might be modified + if (sof != null) { + oldCompSpecs = sof.componentSpecs; + progressive = (sof.tag == JPEG.SOF2); + // Now replace the SOF with a new one; it might be the same, but + // this is easier. + markerSequence.set(markerSequence.indexOf(sof), + new SOFMarkerSegment(progressive, + false, // we never need extended + willSubsample, + ids, + numChannels)); + + // Now suss out if subsampling changed and set the boolean for + // updating the q tables + // if the old componentSpec q table selectors don't match + // the new ones, update the qtables. The new selectors are already + // in place in the new SOF segment above. + for (int i = 0; i < oldCompSpecs.length; i++) { + if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) { + updateQtables = true; + } + } + + if (progressive) { + // if the component ids are different, update all the existing scans + // ignore Huffman tables + boolean idsDiffer = false; + for (int i = 0; i < oldCompSpecs.length; i++) { + if (ids[i] != oldCompSpecs[i].componentId) { + idsDiffer = true; + } + } + if (idsDiffer) { + // update the ids in each SOS marker segment + for (Iterator iter = markerSequence.iterator(); iter.hasNext();) { + MarkerSegment seg = (MarkerSegment) iter.next(); + if (seg instanceof SOSMarkerSegment) { + SOSMarkerSegment target = (SOSMarkerSegment) seg; + for (int i = 0; i < target.componentSpecs.length; i++) { + int oldSelector = + target.componentSpecs[i].componentSelector; + // Find the position in the old componentSpecs array + // of the old component with the old selector + // and replace the component selector with the + // new id at the same position, as these match + // the new component specs array in the SOF created + // above. + for (int j = 0; j < oldCompSpecs.length; j++) { + if (oldCompSpecs[j].componentId == oldSelector) { + target.componentSpecs[i].componentSelector = + ids[j]; + } + } + } + } + } + } + } else { + if (sos != null) { + // htables - if the old htable selectors don't match the new ones, + // update the tables. + for (int i = 0; i < sos.componentSpecs.length; i++) { + if ((sos.componentSpecs[i].dcHuffTable + != newTableSelectors[i]) + || (sos.componentSpecs[i].acHuffTable + != newTableSelectors[i])) { + updateHtables = true; + } + } + + // Might be the same as the old one, but this is easier. + markerSequence.set(markerSequence.indexOf(sos), + new SOSMarkerSegment(willSubsample, + ids, + numChannels)); + } + } + } else { + // should be stream metadata if there isn't an SOF, but check it anyway + if (isStream) { + // update tables - routines below check if it's really necessary + updateQtables = true; + updateHtables = true; + } + } + + if (updateQtables) { + List tableSegments = new ArrayList(); + for (Iterator iter = markerSequence.iterator(); iter.hasNext();) { + MarkerSegment seg = (MarkerSegment) iter.next(); + if (seg instanceof DQTMarkerSegment) { + tableSegments.add(seg); + } + } + // If there are no tables, don't add them, as the metadata encodes an + // abbreviated stream. + // If we are not subsampling, we just need one, so don't do anything + if (!tableSegments.isEmpty() && willSubsample) { + // Is it really necessary? There should be at least 2 tables. + // If there is only one, assume it's a scaled "standard" + // luminance table, extract the scaling factor, and generate a + // scaled "standard" chrominance table. + + // Find the table with selector 1. + boolean found = false; + for (Iterator iter = tableSegments.iterator(); iter.hasNext();) { + DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next(); + for (Iterator tabiter = testdqt.tables.iterator(); + tabiter.hasNext();) { + DQTMarkerSegment.Qtable tab = + (DQTMarkerSegment.Qtable) tabiter.next(); + if (tab.tableID == 1) { + found = true; + } + } + } + if (!found) { + // find the table with selector 0. There should be one. + DQTMarkerSegment.Qtable table0 = null; + for (Iterator iter = tableSegments.iterator(); iter.hasNext();) { + DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next(); + for (Iterator tabiter = testdqt.tables.iterator(); + tabiter.hasNext();) { + DQTMarkerSegment.Qtable tab = + (DQTMarkerSegment.Qtable) tabiter.next(); + if (tab.tableID == 0) { + table0 = tab; + } + } + } + + // Assuming that the table with id 0 is a luminance table, + // compute a new chrominance table of the same quality and + // add it to the last DQT segment + DQTMarkerSegment dqt = + (DQTMarkerSegment) tableSegments.get(tableSegments.size()-1); + dqt.tables.add(dqt.getChromaForLuma(table0)); + } + } + } + + if (updateHtables) { + List tableSegments = new ArrayList(); + for (Iterator iter = markerSequence.iterator(); iter.hasNext();) { + MarkerSegment seg = (MarkerSegment) iter.next(); + if (seg instanceof DHTMarkerSegment) { + tableSegments.add(seg); + } + } + // If there are no tables, don't add them, as the metadata encodes an + // abbreviated stream. + // If we are not subsampling, we just need one, so don't do anything + if (!tableSegments.isEmpty() && willSubsample) { + // Is it really necessary? There should be at least 2 dc and 2 ac + // tables. If there is only one, add a + // "standard " chrominance table. + + // find a table with selector 1. AC/DC is irrelevant + boolean found = false; + for (Iterator iter = tableSegments.iterator(); iter.hasNext();) { + DHTMarkerSegment testdht = (DHTMarkerSegment) iter.next(); + for (Iterator tabiter = testdht.tables.iterator(); + tabiter.hasNext();) { + DHTMarkerSegment.Htable tab = + (DHTMarkerSegment.Htable) tabiter.next(); + if (tab.tableID == 1) { + found = true; + } + } + } + if (!found) { + // Create new standard dc and ac chrominance tables and add them + // to the last DHT segment + DHTMarkerSegment lastDHT = + (DHTMarkerSegment) tableSegments.get(tableSegments.size()-1); + lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1); + lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1); + } + } + } + } + + private boolean wantAlpha(Node transparency) { + boolean returnValue = false; + Node alpha = transparency.getFirstChild(); // Alpha must be first if present + if (alpha.getNodeName().equals("Alpha")) { + if (alpha.hasAttributes()) { + String value = + alpha.getAttributes().getNamedItem("value").getNodeValue(); + if (!value.equals("none")) { + returnValue = true; + } + } + } + transparencyDone = true; + return returnValue; + } + + private void mergeStandardCompressionNode(Node node) + throws IIOInvalidTreeException { + // NumProgressiveScans is ignored. Progression must be enabled on the + // ImageWriteParam. + // No-op + } + + private void mergeStandardDataNode(Node node) + throws IIOInvalidTreeException { + // No-op + } + + private void mergeStandardDimensionNode(Node node) + throws IIOInvalidTreeException { + // Pixel Aspect Ratio or pixel size can be incorporated if there is, + // or can be, a JFIF segment + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true); + if (jfif == null) { + // Can there be one? + // Criteria: + // SOF must be present with 1 or 3 channels, (stream metadata fails this) + // Component ids must be JFIF compatible. + boolean canHaveJFIF = false; + SOFMarkerSegment sof = + (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true); + if (sof != null) { + int numChannels = sof.componentSpecs.length; + if ((numChannels == 1) || (numChannels == 3)) { + canHaveJFIF = true; // remaining tests are negative + for (int i = 0; i < sof.componentSpecs.length; i++) { + if (sof.componentSpecs[i].componentId != i+1) + canHaveJFIF = false; + } + // if Adobe present, transform = ADOBE_UNKNOWN for 1-channel, + // ADOBE_YCC for 3-channel. + AdobeMarkerSegment adobe = + (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, + true); + if (adobe != null) { + if (adobe.transform != ((numChannels == 1) + ? JPEG.ADOBE_UNKNOWN + : JPEG.ADOBE_YCC)) { + canHaveJFIF = false; + } + } + } + } + // If so, create one and insert it into the sequence. Note that + // default is just pixel ratio at 1:1 + if (canHaveJFIF) { + jfif = new JFIFMarkerSegment(); + markerSequence.add(0, jfif); + } + } + if (jfif != null) { + NodeList children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + NamedNodeMap attrs = child.getAttributes(); + String name = child.getNodeName(); + if (name.equals("PixelAspectRatio")) { + String valueString = attrs.getNamedItem("value").getNodeValue(); + float value = Float.parseFloat(valueString); + Point p = findIntegerRatio(value); + jfif.resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO; + jfif.Xdensity = p.x; + jfif.Xdensity = p.y; + } else if (name.equals("HorizontalPixelSize")) { + String valueString = attrs.getNamedItem("value").getNodeValue(); + float value = Float.parseFloat(valueString); + // Convert from mm/dot to dots/cm + int dpcm = (int) Math.round(1.0/(value*10.0)); + jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM; + jfif.Xdensity = dpcm; + } else if (name.equals("VerticalPixelSize")) { + String valueString = attrs.getNamedItem("value").getNodeValue(); + float value = Float.parseFloat(valueString); + // Convert from mm/dot to dots/cm + int dpcm = (int) Math.round(1.0/(value*10.0)); + jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM; + jfif.Ydensity = dpcm; + } + + } + } + } + + /* + * Return a pair of integers whose ratio (x/y) approximates the given + * float value. + */ + private static Point findIntegerRatio(float value) { + float epsilon = 0.005F; + + // Normalize + value = Math.abs(value); + + // Deal with min case + if (value <= epsilon) { + return new Point(1, 255); + } + + // Deal with max case + if (value >= 255) { + return new Point(255, 1); + } + + // Remember if we invert + boolean inverted = false; + if (value < 1.0) { + value = 1.0F/value; + inverted = true; + } + + // First approximation + int y = 1; + int x = (int) Math.round(value); + + float ratio = (float) x; + float delta = Math.abs(value - ratio); + while (delta > epsilon) { // not close enough + // Increment y and compute a new x + y++; + x = (int) Math.round(y*value); + ratio = (float)x/(float)y; + delta = Math.abs(value - ratio); + } + return inverted ? new Point(y, x) : new Point(x, y); + } + + private void mergeStandardDocumentNode(Node node) + throws IIOInvalidTreeException { + // No-op + } + + private void mergeStandardTextNode(Node node) + throws IIOInvalidTreeException { + // Convert to comments. For the moment ignore the encoding issue. + // Ignore keywords, language, and encoding (for the moment). + // If compression tag is present, use only entries with "none". + NodeList children = node.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + NamedNodeMap attrs = child.getAttributes(); + Node comp = attrs.getNamedItem("compression"); + boolean copyIt = true; + if (comp != null) { + String compString = comp.getNodeValue(); + if (!compString.equals("none")) { + copyIt = false; + } + } + if (copyIt) { + String value = attrs.getNamedItem("value").getNodeValue(); + COMMarkerSegment com = new COMMarkerSegment(value); + insertCOMMarkerSegment(com); + } + } + } + + private void mergeStandardTransparencyNode(Node node) + throws IIOInvalidTreeException { + // This might indicate that an alpha channel is being added or removed. + // The nodes must appear in order, and a Chroma node will process any + // transparency, so process it here only if there was no Chroma node + // Do nothing for stream metadata + if (!transparencyDone && !isStream) { + boolean wantAlpha = wantAlpha(node); + // do we have alpha already? If the number of channels is 2 or 4, + // we do, as we don't support CMYK, nor can we add alpha to it + // The number of channels can be determined from the SOF + JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment + (JFIFMarkerSegment.class, true); + AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment + (AdobeMarkerSegment.class, true); + SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment + (SOFMarkerSegment.class, true); + SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment + (SOSMarkerSegment.class, true); + + // We can do nothing for progressive, as we don't know how to + // modify the scans. + if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive + return; + } + + // Do we already have alpha? We can tell by the number of channels + // We must have an sof, or we can't do anything further + if (sof != null) { + int numChannels = sof.componentSpecs.length; + boolean hadAlpha = (numChannels == 2) || (numChannels == 4); + // proceed only if the old state and the new state differ + if (hadAlpha != wantAlpha) { + if (wantAlpha) { // Adding alpha + numChannels++; + if (jfif != null) { + markerSequence.remove(jfif); + } + + // If an adobe marker is present, transform must be UNKNOWN + if (adobe != null) { + adobe.transform = JPEG.ADOBE_UNKNOWN; + } + + // Add a component spec with appropriate parameters to SOF + SOFMarkerSegment.ComponentSpec [] newSpecs = + new SOFMarkerSegment.ComponentSpec[numChannels]; + for (int i = 0; i < sof.componentSpecs.length; i++) { + newSpecs[i] = sof.componentSpecs[i]; + } + byte oldFirstID = (byte) sof.componentSpecs[0].componentId; + byte newID = (byte) ((oldFirstID > 1) ? 'A' : 4); + newSpecs[numChannels-1] = + sof.getComponentSpec(newID, + sof.componentSpecs[0].HsamplingFactor, + sof.componentSpecs[0].QtableSelector); + + sof.componentSpecs = newSpecs; + + // Add a component spec with appropriate parameters to SOS + SOSMarkerSegment.ScanComponentSpec [] newScanSpecs = + new SOSMarkerSegment.ScanComponentSpec [numChannels]; + for (int i = 0; i < sos.componentSpecs.length; i++) { + newScanSpecs[i] = sos.componentSpecs[i]; + } + newScanSpecs[numChannels-1] = + sos.getScanComponentSpec (newID, 0); + sos.componentSpecs = newScanSpecs; + } else { // Removing alpha + numChannels--; + // Remove a component spec from SOF + SOFMarkerSegment.ComponentSpec [] newSpecs = + new SOFMarkerSegment.ComponentSpec[numChannels]; + for (int i = 0; i < numChannels; i++) { + newSpecs[i] = sof.componentSpecs[i]; + } + sof.componentSpecs = newSpecs; + + // Remove a component spec from SOS + SOSMarkerSegment.ScanComponentSpec [] newScanSpecs = + new SOSMarkerSegment.ScanComponentSpec [numChannels]; + for (int i = 0; i < numChannels; i++) { + newScanSpecs[i] = sos.componentSpecs[i]; + } + sos.componentSpecs = newScanSpecs; + } + } + } + } + } + + + public void setFromTree(String formatName, Node root) + throws IIOInvalidTreeException { + if (formatName == null) { + throw new IllegalArgumentException("null formatName!"); + } + if (root == null) { + throw new IllegalArgumentException("null root!"); + } + if (isStream && + (formatName.equals(JPEG.nativeStreamMetadataFormatName))) { + setFromNativeTree(root); + } else if (!isStream && + (formatName.equals(JPEG.nativeImageMetadataFormatName))) { + setFromNativeTree(root); + } else if (!isStream && + (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName))) { + // In this case a reset followed by a merge is correct + super.setFromTree(formatName, root); + } else { + throw new IllegalArgumentException("Unsupported format name: " + + formatName); + } + } + + private void setFromNativeTree(Node root) throws IIOInvalidTreeException { + if (resetSequence == null) { + resetSequence = markerSequence; + } + markerSequence = new ArrayList(); + + // Build a whole new marker sequence from the tree + + String name = root.getNodeName(); + if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName + : JPEG.nativeImageMetadataFormatName)) { + throw new IIOInvalidTreeException("Invalid root node name: " + name, + root); + } + if (!isStream) { + if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence + throw new IIOInvalidTreeException( + "JPEGvariety and markerSequence nodes must be present", root); + } + + Node JPEGvariety = root.getFirstChild(); + + if (JPEGvariety.getChildNodes().getLength() != 0) { + markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild())); + } + } + + Node markerSequenceNode = isStream ? root : root.getLastChild(); + setFromMarkerSequenceNode(markerSequenceNode); + + } + + void setFromMarkerSequenceNode(Node markerSequenceNode) + throws IIOInvalidTreeException{ + + NodeList children = markerSequenceNode.getChildNodes(); + // for all the children, add a marker segment + for (int i = 0; i < children.getLength(); i++) { + Node node = children.item(i); + String childName = node.getNodeName(); + if (childName.equals("dqt")) { + markerSequence.add(new DQTMarkerSegment(node)); + } else if (childName.equals("dht")) { + markerSequence.add(new DHTMarkerSegment(node)); + } else if (childName.equals("dri")) { + markerSequence.add(new DRIMarkerSegment(node)); + } else if (childName.equals("com")) { + markerSequence.add(new COMMarkerSegment(node)); + } else if (childName.equals("app14Adobe")) { + markerSequence.add(new AdobeMarkerSegment(node)); + } else if (childName.equals("unknown")) { + markerSequence.add(new MarkerSegment(node)); + } else if (childName.equals("sof")) { + markerSequence.add(new SOFMarkerSegment(node)); + } else if (childName.equals("sos")) { + markerSequence.add(new SOSMarkerSegment(node)); + } else { + throw new IIOInvalidTreeException("Invalid " + + (isStream ? "stream " : "image ") + "child: " + + childName, node); + } + } + } + + /** + * Check that this metadata object is in a consistent state and + * return true if it is or false + * otherwise. All the constructors and modifiers should call + * this method at the end to guarantee that the data is always + * consistent, as the writer relies on this. + */ + private boolean isConsistent() { + SOFMarkerSegment sof = + (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, + true); + JFIFMarkerSegment jfif = + (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, + true); + AdobeMarkerSegment adobe = + (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, + true); + boolean retval = true; + if (!isStream) { + if (sof != null) { + // SOF numBands = total scan bands + int numSOFBands = sof.componentSpecs.length; + int numScanBands = countScanBands(); + if (numScanBands != 0) { // No SOS is OK + if (numScanBands != numSOFBands) { + retval = false; + } + } + // If JFIF is present, component ids are 1-3, bands are 1 or 3 + if (jfif != null) { + if ((numSOFBands != 1) && (numSOFBands != 3)) { + retval = false; + } + for (int i = 0; i < numSOFBands; i++) { + if (sof.componentSpecs[i].componentId != i+1) { + retval = false; + } + } + + // If both JFIF and Adobe are present, + // Adobe transform == unknown for gray, + // YCC for 3-chan. + if ((adobe != null) + && (((numSOFBands == 1) + && (adobe.transform != JPEG.ADOBE_UNKNOWN)) + || ((numSOFBands == 3) + && (adobe.transform != JPEG.ADOBE_YCC)))) { + retval = false; + } + } + } else { + // stream can't have jfif, adobe, sof, or sos + SOSMarkerSegment sos = + (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, + true); + if ((jfif != null) || (adobe != null) + || (sof != null) || (sos != null)) { + retval = false; + } + } + } + return retval; + } + + /** + * Returns the total number of bands referenced in all SOS marker + * segments, including 0 if there are no SOS marker segments. + */ + private int countScanBands() { + List ids = new ArrayList(); + Iterator iter = markerSequence.iterator(); + while(iter.hasNext()) { + MarkerSegment seg = (MarkerSegment)iter.next(); + if (seg instanceof SOSMarkerSegment) { + SOSMarkerSegment sos = (SOSMarkerSegment) seg; + SOSMarkerSegment.ScanComponentSpec [] specs = sos.componentSpecs; + for (int i = 0; i < specs.length; i++) { + Integer id = new Integer(specs[i].componentSelector); + if (!ids.contains(id)) { + ids.add(id); + } + } + } + } + + return ids.size(); + } + + ///// Writer support + + void writeToStream(ImageOutputStream ios, + boolean ignoreJFIF, + boolean forceJFIF, + List thumbnails, + ICC_Profile iccProfile, + boolean ignoreAdobe, + int newAdobeTransform, + JPEGImageWriter writer) + throws IOException { + if (forceJFIF) { + // Write a default JFIF segment, including thumbnails + // This won't be duplicated below because forceJFIF will be + // set only if there is no JFIF present already. + JFIFMarkerSegment.writeDefaultJFIF(ios, + thumbnails, + iccProfile, + writer); + if ((ignoreAdobe == false) + && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) { + if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN) + && (newAdobeTransform != JPEG.ADOBE_YCC)) { + // Not compatible, so ignore Adobe. + ignoreAdobe = true; + writer.warningOccurred + (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB); + } + } + } + // Iterate over each MarkerSegment + Iterator iter = markerSequence.iterator(); + while(iter.hasNext()) { + MarkerSegment seg = (MarkerSegment)iter.next(); + if (seg instanceof JFIFMarkerSegment) { + if (ignoreJFIF == false) { + JFIFMarkerSegment jfif = (JFIFMarkerSegment) seg; + jfif.writeWithThumbs(ios, thumbnails, writer); + if (iccProfile != null) { + JFIFMarkerSegment.writeICC(iccProfile, ios); + } + } // Otherwise ignore it, as requested + } else if (seg instanceof AdobeMarkerSegment) { + if (ignoreAdobe == false) { + if (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE) { + AdobeMarkerSegment newAdobe = + (AdobeMarkerSegment) seg.clone(); + newAdobe.transform = newAdobeTransform; + newAdobe.write(ios); + } else if (forceJFIF) { + // If adobe isn't JFIF compatible, ignore it + AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg; + if ((adobe.transform == JPEG.ADOBE_UNKNOWN) + || (adobe.transform == JPEG.ADOBE_YCC)) { + adobe.write(ios); + } else { + writer.warningOccurred + (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB); + } + } else { + seg.write(ios); + } + } // Otherwise ignore it, as requested + } else { + seg.write(ios); + } + } + } + + //// End of writer support + + public void reset() { + if (resetSequence != null) { // Otherwise no need to reset + markerSequence = resetSequence; + resetSequence = null; + } + } + + public void print() { + for (int i = 0; i < markerSequence.size(); i++) { + MarkerSegment seg = (MarkerSegment) markerSequence.get(i); + seg.print(); + } + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadataFormat.java new file mode 100644 index 000000000..a7c116962 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadataFormat.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.plugins.jpeg.JPEGQTable; +import javax.imageio.plugins.jpeg.JPEGHuffmanTable; + +import java.util.List; +import java.util.ArrayList; + +abstract class JPEGMetadataFormat extends IIOMetadataFormatImpl { + // 2-byte length reduces max to 65533 + private static final int MAX_JPEG_DATA_SIZE = 65533; + + String resourceBaseName = this.getClass().getName() + "Resources"; + + JPEGMetadataFormat(String formatName, int childPolicy) { + super(formatName, childPolicy); + setResourceBaseName(resourceBaseName); + } + + // Format shared between image and stream formats + void addStreamElements(String parentName) { + addElement("dqt", parentName, 1, 4); + + addElement("dqtable", "dqt", CHILD_POLICY_EMPTY); + + addAttribute("dqtable", + "elementPrecision", + DATATYPE_INTEGER, + false, + "0"); + List tabids = new ArrayList(); + tabids.add("0"); + tabids.add("1"); + tabids.add("2"); + tabids.add("3"); + addAttribute("dqtable", + "qtableId", + DATATYPE_INTEGER, + true, + null, + tabids); + addObjectValue("dqtable", + JPEGQTable.class, + true, + null); + + addElement("dht", parentName, 1, 4); + addElement("dhtable", "dht", CHILD_POLICY_EMPTY); + List classes = new ArrayList(); + classes.add("0"); + classes.add("1"); + addAttribute("dhtable", + "class", + DATATYPE_INTEGER, + true, + null, + classes); + addAttribute("dhtable", + "htableId", + DATATYPE_INTEGER, + true, + null, + tabids); + addObjectValue("dhtable", + JPEGHuffmanTable.class, + true, + null); + + + addElement("dri", parentName, CHILD_POLICY_EMPTY); + addAttribute("dri", + "interval", + DATATYPE_INTEGER, + true, + null, + "0", "65535", + true, true); + + addElement("com", parentName, CHILD_POLICY_EMPTY); + addAttribute("com", + "comment", + DATATYPE_STRING, + false, + null); + addObjectValue("com", byte[].class, 1, MAX_JPEG_DATA_SIZE); + + addElement("unknown", parentName, CHILD_POLICY_EMPTY); + addAttribute("unknown", + "MarkerTag", + DATATYPE_INTEGER, + true, + null, + "0", "255", + true, true); + addObjectValue("unknown", byte[].class, 1, MAX_JPEG_DATA_SIZE); + } + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + // Just check if it appears in the format + if (isInSubtree(elementName, getRootName())){ + return true; + } + return false; + } + + /** + * Returns true if the named element occurs in the + * subtree of the format starting with the node named by + * subtreeName, including the node + * itself. subtreeName may be any node in + * the format. If it is not, an + * IllegalArgumentException is thrown. + */ + protected boolean isInSubtree(String elementName, + String subtreeName) { + if (elementName.equals(subtreeName)) { + return true; + } + String [] children = getChildNames(elementName); + for (int i=0; i < children.length; i++) { + if (isInSubtree(elementName, children[i])) { + return true; + } + } + return false; + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadataFormatResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadataFormatResources.java new file mode 100644 index 000000000..fb245e131 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGMetadataFormatResources.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import java.util.ListResourceBundle; + +abstract class JPEGMetadataFormatResources + extends ListResourceBundle { + + static final Object[][] commonContents = { + // Node name, followed by description + { "dqt", "A Define Quantization Table(s) marker segment" }, + { "dqtable", "A single quantization table" }, + { "dht", "A Define Huffman Table(s) marker segment" }, + { "dhtable", "A single Huffman table" }, + { "dri", "A Define Restart Interval marker segment" }, + { "com", "A Comment marker segment. The user object contains " + + "the actual bytes."}, + { "unknown", "An unrecognized marker segment. The user object " + + "contains the data not including length." }, + + // Node name + "/" + AttributeName, followed by description + { "dqtable/elementPrecision", + "The number of bits in each table element (0 = 8, 1 = 16)" }, + { "dgtable/qtableId", + "The table id" }, + { "dhtable/class", + "Indicates whether this is a DC (0) or an AC (1) table" }, + { "dhtable/htableId", + "The table id" }, + { "dri/interval", + "The restart interval in MCUs" }, + { "com/comment", + "The comment as a string (used only if user object is null)" }, + { "unknown/MarkerTag", + "The tag identifying this marker segment" } + }; +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormat.java new file mode 100644 index 000000000..1e2114925 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormat.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class JPEGStreamMetadataFormat extends JPEGMetadataFormat { + + private static JPEGStreamMetadataFormat theInstance = null; + + private JPEGStreamMetadataFormat() { + super(JPEG.nativeStreamMetadataFormatName, + CHILD_POLICY_SEQUENCE); + addStreamElements(getRootName()); + } + + public static synchronized IIOMetadataFormat getInstance() { + if (theInstance == null) { + theInstance = new JPEGStreamMetadataFormat(); + } + return theInstance; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormatResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormatResources.java new file mode 100644 index 000000000..ea6d2b701 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/JPEGStreamMetadataFormatResources.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import java.util.ListResourceBundle; + +public class JPEGStreamMetadataFormatResources + extends JPEGMetadataFormatResources { + + public JPEGStreamMetadataFormatResources() {} + + protected Object[][] getContents() { + // return a copy of commonContents; in theory we want a deep clone + // of commonContents, but since it only contains (immutable) Strings, + // this shallow copy is sufficient + Object[][] commonCopy = new Object[commonContents.length][2]; + for (int i = 0; i < commonContents.length; i++) { + commonCopy[i][0] = commonContents[i][0]; + commonCopy[i][1] = commonContents[i][1]; + } + return commonCopy; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/MarkerSegment.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/MarkerSegment.java new file mode 100644 index 000000000..2ca0b8c8f --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/MarkerSegment.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.IIOException; + +import java.io.IOException; + +import org.w3c.dom.Node; +import org.w3c.dom.NamedNodeMap; + +/** + * All metadata is stored in MarkerSegments. Marker segments + * that we know about are stored in subclasses of this + * basic class, which used for unrecognized APPn marker + * segments. XXX break out UnknownMarkerSegment as a subclass + * and make this abstract, avoiding unused data field. + */ +class MarkerSegment implements Cloneable { + protected static final int LENGTH_SIZE = 2; // length is 2 bytes + int tag; // See JPEG.java + int length; /* Sometimes needed by subclasses; doesn't include + itself. Meaningful only if constructed from a stream */ + byte [] data = null; // Raw segment data, used for unrecognized segments + boolean unknown = false; // Set to true if the tag is not recognized + + /** + * Constructor for creating MarkerSegments by reading + * from an ImageInputStream. + */ + MarkerSegment(JPEGBuffer buffer) throws IOException { + + buffer.loadBuf(3); // tag plus length + tag = buffer.buf[buffer.bufPtr++] & 0xff; + length = (buffer.buf[buffer.bufPtr++] & 0xff) << 8; + length |= buffer.buf[buffer.bufPtr++] & 0xff; + length -= 2; // JPEG length includes itself, we don't + + if (length < 0) { + throw new IIOException("Invalid segment length: " + length); + } + buffer.bufAvail -= 3; + // Now that we know the true length, ensure that we've got it, + // or at least a bufferful if length is too big. + buffer.loadBuf(length); + } + + /** + * Constructor used when creating segments other than by + * reading them from a stream. + */ + MarkerSegment(int tag) { + this.tag = tag; + length = 0; + } + + /** + * Construct a MarkerSegment from an "unknown" DOM Node. + */ + MarkerSegment(Node node) throws IIOInvalidTreeException { + // The type of node should have been verified already. + // get the attribute and assign it to the tag + tag = getAttributeValue(node, + null, + "MarkerTag", + 0, 255, + true); + length = 0; + // get the user object and clone it to the data + if (node instanceof IIOMetadataNode) { + IIOMetadataNode iioNode = (IIOMetadataNode) node; + try { + data = (byte []) iioNode.getUserObject(); + } catch (Exception e) { + IIOInvalidTreeException newGuy = + new IIOInvalidTreeException + ("Can't get User Object", node); + newGuy.initCause(e); + throw newGuy; + } + } else { + throw new IIOInvalidTreeException + ("Node must have User Object", node); + } + } + + /** + * Deep copy of data array. + */ + protected Object clone() { + MarkerSegment newGuy = null; + try { + newGuy = (MarkerSegment) super.clone(); + } catch (CloneNotSupportedException e) {} // won't happen + if (this.data != null) { + newGuy.data = (byte[]) data.clone(); + } + return newGuy; + } + + /** + * We have determined that we don't know the type, so load + * the data using the length parameter. + */ + void loadData(JPEGBuffer buffer) throws IOException { + data = new byte[length]; + buffer.readData(data); + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("unknown"); + node.setAttribute("MarkerTag", Integer.toString(tag)); + node.setUserObject(data); + + return node; + } + + static int getAttributeValue(Node node, + NamedNodeMap attrs, + String name, + int min, + int max, + boolean required) + throws IIOInvalidTreeException { + if (attrs == null) { + attrs = node.getAttributes(); + } + String valueString = attrs.getNamedItem(name).getNodeValue(); + int value = -1; + if (valueString == null) { + if (required) { + throw new IIOInvalidTreeException + (name + " attribute not found", node); + } + } else { + value = Integer.parseInt(valueString); + if ((value < min) || (value > max)) { + throw new IIOInvalidTreeException + (name + " attribute out of range", node); + } + } + return value; + } + + /** + * Writes the marker, tag, and length. Note that length + * should be verified by the caller as a correct JPEG + * length, i.e it includes itself. + */ + void writeTag(ImageOutputStream ios) throws IOException { + ios.write(0xff); + ios.write(tag); + write2bytes(ios, length); + } + + /** + * Writes the data for this segment to the stream in + * valid JPEG format. + */ + void write(ImageOutputStream ios) throws IOException { + length = 2 + ((data != null) ? data.length : 0); + writeTag(ios); + if (data != null) { + ios.write(data); + } + } + + static void write2bytes(ImageOutputStream ios, + int value) throws IOException { + ios.write((value >> 8) & 0xff); + ios.write(value & 0xff); + + } + + void printTag(String prefix) { + System.out.println(prefix + " marker segment - marker = 0x" + + Integer.toHexString(tag)); + System.out.println("length: " + length); + } + + void print() { + printTag("Unknown"); + if (length > 10) { + System.out.print("First 5 bytes:"); + for (int i=0;i<5;i++) { + System.out.print(" Ox" + + Integer.toHexString((int)data[i])); + } + System.out.print("\nLast 5 bytes:"); + for (int i=data.length-5;i>> 4; + VsamplingFactor = buffer.buf[buffer.bufPtr++] & 0xf; + QtableSelector = buffer.buf[buffer.bufPtr++]; + } + + ComponentSpec(Node node) throws IIOInvalidTreeException { + NamedNodeMap attrs = node.getAttributes(); + componentId = getAttributeValue(node, attrs, "componentId", 0, 255, true); + HsamplingFactor = getAttributeValue(node, attrs, "HsamplingFactor", + 1, 255, true); + VsamplingFactor = getAttributeValue(node, attrs, "VsamplingFactor", + 1, 255, true); + QtableSelector = getAttributeValue(node, attrs, "QtableSelector", + 0, 3, true); + } + + protected Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) {} // won't happen + return null; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("componentSpec"); + node.setAttribute("componentId", + Integer.toString(componentId)); + node.setAttribute("HsamplingFactor", + Integer.toString(HsamplingFactor)); + node.setAttribute("VsamplingFactor", + Integer.toString(VsamplingFactor)); + node.setAttribute("QtableSelector", + Integer.toString(QtableSelector)); + return node; + } + + void print () { + System.out.print("Component ID: "); + System.out.println(componentId); + System.out.print("H sampling factor: "); + System.out.println(HsamplingFactor); + System.out.print("V sampling factor: "); + System.out.println(VsamplingFactor); + System.out.print("Q table selector: "); + System.out.println(QtableSelector); + } + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/SOSMarkerSegment.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/SOSMarkerSegment.java new file mode 100644 index 000000000..afffa7bd7 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/jpeg/SOSMarkerSegment.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.jpeg; + +//import javax.imageio.IIOException; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageOutputStream; + +import java.io.IOException; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.NamedNodeMap; + +/** + * An SOS (Start Of Scan) marker segment. + */ +class SOSMarkerSegment extends MarkerSegment { + int startSpectralSelection; + int endSpectralSelection; + int approxHigh; + int approxLow; + ScanComponentSpec [] componentSpecs; // Array size is numScanComponents + + SOSMarkerSegment(boolean willSubsample, + byte[] componentIDs, + int numComponents) { + super(JPEG.SOS); + startSpectralSelection = 0; + endSpectralSelection = 63; + approxHigh = 0; + approxLow = 0; + componentSpecs = new ScanComponentSpec[numComponents]; + for (int i = 0; i < numComponents; i++) { + int tableSel = 0; + if (willSubsample) { + if ((i == 1) || (i == 2)) { + tableSel = 1; + } + } + componentSpecs[i] = new ScanComponentSpec(componentIDs[i], + tableSel); + } + } + + SOSMarkerSegment(JPEGBuffer buffer) throws IOException { + super(buffer); + int numComponents = buffer.buf[buffer.bufPtr++]; + componentSpecs = new ScanComponentSpec[numComponents]; + for (int i = 0; i < numComponents; i++) { + componentSpecs[i] = new ScanComponentSpec(buffer); + } + startSpectralSelection = buffer.buf[buffer.bufPtr++]; + endSpectralSelection = buffer.buf[buffer.bufPtr++]; + approxHigh = buffer.buf[buffer.bufPtr] >> 4; + approxLow = buffer.buf[buffer.bufPtr++] &0xf; + buffer.bufAvail -= length; + } + + SOSMarkerSegment(Node node) throws IIOInvalidTreeException { + super(JPEG.SOS); + startSpectralSelection = 0; + endSpectralSelection = 63; + approxHigh = 0; + approxLow = 0; + updateFromNativeNode(node, true); + } + + protected Object clone () { + SOSMarkerSegment newGuy = (SOSMarkerSegment) super.clone(); + if (componentSpecs != null) { + newGuy.componentSpecs = + (ScanComponentSpec []) componentSpecs.clone(); + for (int i = 0; i < componentSpecs.length; i++) { + newGuy.componentSpecs[i] = + (ScanComponentSpec) componentSpecs[i].clone(); + } + } + return newGuy; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("sos"); + node.setAttribute("numScanComponents", + Integer.toString(componentSpecs.length)); + node.setAttribute("startSpectralSelection", + Integer.toString(startSpectralSelection)); + node.setAttribute("endSpectralSelection", + Integer.toString(endSpectralSelection)); + node.setAttribute("approxHigh", + Integer.toString(approxHigh)); + node.setAttribute("approxLow", + Integer.toString(approxLow)); + for (int i = 0; i < componentSpecs.length; i++) { + node.appendChild(componentSpecs[i].getNativeNode()); + } + + return node; + } + + void updateFromNativeNode(Node node, boolean fromScratch) + throws IIOInvalidTreeException { + NamedNodeMap attrs = node.getAttributes(); + int numComponents = getAttributeValue(node, attrs, "numScanComponents", + 1, 4, true); + int value = getAttributeValue(node, attrs, "startSpectralSelection", + 0, 63, false); + startSpectralSelection = (value != -1) ? value : startSpectralSelection; + value = getAttributeValue(node, attrs, "endSpectralSelection", + 0, 63, false); + endSpectralSelection = (value != -1) ? value : endSpectralSelection; + value = getAttributeValue(node, attrs, "approxHigh", 0, 15, false); + approxHigh = (value != -1) ? value : approxHigh; + value = getAttributeValue(node, attrs, "approxLow", 0, 15, false); + approxLow = (value != -1) ? value : approxLow; + + // Now the children + NodeList children = node.getChildNodes(); + if (children.getLength() != numComponents) { + throw new IIOInvalidTreeException + ("numScanComponents must match the number of children", node); + } + componentSpecs = new ScanComponentSpec[numComponents]; + for (int i = 0; i < numComponents; i++) { + componentSpecs[i] = new ScanComponentSpec(children.item(i)); + } + } + + /** + * Writes the data for this segment to the stream in + * valid JPEG format. + */ + void write(ImageOutputStream ios) throws IOException { + // We don't write SOS segments; the IJG library does. + } + + void print () { + printTag("SOS"); + System.out.print("Start spectral selection: "); + System.out.println(startSpectralSelection); + System.out.print("End spectral selection: "); + System.out.println(endSpectralSelection); + System.out.print("Approx high: "); + System.out.println(approxHigh); + System.out.print("Approx low: "); + System.out.println(approxLow); + System.out.print("Num scan components: "); + System.out.println(componentSpecs.length); + for (int i = 0; i< componentSpecs.length; i++) { + componentSpecs[i].print(); + } + } + + ScanComponentSpec getScanComponentSpec(byte componentSel, int tableSel) { + return new ScanComponentSpec(componentSel, tableSel); + } + + /** + * A scan component spec within an SOS marker segment. + */ + class ScanComponentSpec implements Cloneable { + int componentSelector; + int dcHuffTable; + int acHuffTable; + + ScanComponentSpec(byte componentSel, int tableSel) { + componentSelector = componentSel; + dcHuffTable = tableSel; + acHuffTable = tableSel; + } + + ScanComponentSpec(JPEGBuffer buffer) { + // Parent already loaded the buffer + componentSelector = buffer.buf[buffer.bufPtr++]; + dcHuffTable = buffer.buf[buffer.bufPtr] >> 4; + acHuffTable = buffer.buf[buffer.bufPtr++] & 0xf; + } + + ScanComponentSpec(Node node) throws IIOInvalidTreeException { + NamedNodeMap attrs = node.getAttributes(); + componentSelector = getAttributeValue(node, attrs, "componentSelector", + 0, 255, true); + dcHuffTable = getAttributeValue(node, attrs, "dcHuffTable", + 0, 3, true); + acHuffTable = getAttributeValue(node, attrs, "acHuffTable", + 0, 3, true); + } + + protected Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) {} // won't happen + return null; + } + + IIOMetadataNode getNativeNode() { + IIOMetadataNode node = new IIOMetadataNode("scanComponentSpec"); + node.setAttribute("componentSelector", + Integer.toString(componentSelector)); + node.setAttribute("dcHuffTable", + Integer.toString(dcHuffTable)); + node.setAttribute("acHuffTable", + Integer.toString(acHuffTable)); + return node; + } + + void print () { + System.out.print("Component Selector: "); + System.out.println(componentSelector); + System.out.print("DC huffman table: "); + System.out.println(dcHuffTable); + System.out.print("AC huffman table: "); + System.out.println(acHuffTable); + } + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageReader.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageReader.java new file mode 100644 index 000000000..ffd11bd47 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageReader.java @@ -0,0 +1,1622 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferUShort; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.InputStream; +import java.io.IOException; +import java.io.SequenceInputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import javax.imageio.IIOException; +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import com.sun.imageio.plugins.common.InputStreamAdapter; +import com.sun.imageio.plugins.common.ReaderUtil; +import com.sun.imageio.plugins.common.SubImageInputStream; +import java.io.ByteArrayOutputStream; +import sun.awt.image.ByteInterleavedRaster; + +class PNGImageDataEnumeration implements Enumeration { + + boolean firstTime = true; + ImageInputStream stream; + int length; + + public PNGImageDataEnumeration(ImageInputStream stream) + throws IOException { + this.stream = stream; + this.length = stream.readInt(); + int type = stream.readInt(); // skip chunk type + } + + public InputStream nextElement() { + try { + firstTime = false; + ImageInputStream iis = new SubImageInputStream(stream, length); + return new InputStreamAdapter(iis); + } catch (IOException e) { + return null; + } + } + + public boolean hasMoreElements() { + if (firstTime) { + return true; + } + + try { + int crc = stream.readInt(); + this.length = stream.readInt(); + int type = stream.readInt(); + if (type == PNGImageReader.IDAT_TYPE) { + return true; + } else { + return false; + } + } catch (IOException e) { + return false; + } + } +} + +public class PNGImageReader extends ImageReader { + + /* + * Note: The following chunk type constants are autogenerated. Each + * one is derived from the ASCII values of its 4-character name. For + * example, IHDR_TYPE is calculated as follows: + * ('I' << 24) | ('H' << 16) | ('D' << 8) | 'R' + */ + + // Critical chunks + static final int IHDR_TYPE = 0x49484452; + static final int PLTE_TYPE = 0x504c5445; + static final int IDAT_TYPE = 0x49444154; + static final int IEND_TYPE = 0x49454e44; + + // Ancillary chunks + static final int bKGD_TYPE = 0x624b4744; + static final int cHRM_TYPE = 0x6348524d; + static final int gAMA_TYPE = 0x67414d41; + static final int hIST_TYPE = 0x68495354; + static final int iCCP_TYPE = 0x69434350; + static final int iTXt_TYPE = 0x69545874; + static final int pHYs_TYPE = 0x70485973; + static final int sBIT_TYPE = 0x73424954; + static final int sPLT_TYPE = 0x73504c54; + static final int sRGB_TYPE = 0x73524742; + static final int tEXt_TYPE = 0x74455874; + static final int tIME_TYPE = 0x74494d45; + static final int tRNS_TYPE = 0x74524e53; + static final int zTXt_TYPE = 0x7a545874; + + static final int PNG_COLOR_GRAY = 0; + static final int PNG_COLOR_RGB = 2; + static final int PNG_COLOR_PALETTE = 3; + static final int PNG_COLOR_GRAY_ALPHA = 4; + static final int PNG_COLOR_RGB_ALPHA = 6; + + // The number of bands by PNG color type + static final int[] inputBandsForColorType = { + 1, // gray + -1, // unused + 3, // rgb + 1, // palette + 2, // gray + alpha + -1, // unused + 4 // rgb + alpha + }; + + static final int PNG_FILTER_NONE = 0; + static final int PNG_FILTER_SUB = 1; + static final int PNG_FILTER_UP = 2; + static final int PNG_FILTER_AVERAGE = 3; + static final int PNG_FILTER_PAETH = 4; + + static final int[] adam7XOffset = { 0, 4, 0, 2, 0, 1, 0 }; + static final int[] adam7YOffset = { 0, 0, 4, 0, 2, 0, 1 }; + static final int[] adam7XSubsampling = { 8, 8, 4, 4, 2, 2, 1, 1 }; + static final int[] adam7YSubsampling = { 8, 8, 8, 4, 4, 2, 2, 1 }; + + private static final boolean debug = true; + + ImageInputStream stream = null; + + boolean gotHeader = false; + boolean gotMetadata = false; + + ImageReadParam lastParam = null; + + long imageStartPosition = -1L; + + Rectangle sourceRegion = null; + int sourceXSubsampling = -1; + int sourceYSubsampling = -1; + int sourceMinProgressivePass = 0; + int sourceMaxProgressivePass = 6; + int[] sourceBands = null; + int[] destinationBands = null; + Point destinationOffset = new Point(0, 0); + + PNGMetadata metadata = new PNGMetadata(); + + DataInputStream pixelStream = null; + + BufferedImage theImage = null; + + // The number of source pixels processed + int pixelsDone = 0; + + // The total number of pixels in the source image + int totalPixels; + + public PNGImageReader(ImageReaderSpi originatingProvider) { + super(originatingProvider); + } + + public void setInput(Object input, + boolean seekForwardOnly, + boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + this.stream = (ImageInputStream)input; // Always works + + // Clear all values based on the previous stream contents + resetStreamSettings(); + } + + private String readNullTerminatedString(String charset, int maxLen) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int b; + int count = 0; + while ((maxLen > count++) && ((b = stream.read()) != 0)) { + if (b == -1) throw new EOFException(); + baos.write(b); + } + return new String(baos.toByteArray(), charset); + } + + private void readHeader() throws IIOException { + if (gotHeader) { + return; + } + if (stream == null) { + throw new IllegalStateException("Input source not set!"); + } + + try { + byte[] signature = new byte[8]; + stream.readFully(signature); + + if (signature[0] != (byte)137 || + signature[1] != (byte)80 || + signature[2] != (byte)78 || + signature[3] != (byte)71 || + signature[4] != (byte)13 || + signature[5] != (byte)10 || + signature[6] != (byte)26 || + signature[7] != (byte)10) { + throw new IIOException("Bad PNG signature!"); + } + + int IHDR_length = stream.readInt(); + if (IHDR_length != 13) { + throw new IIOException("Bad length for IHDR chunk!"); + } + int IHDR_type = stream.readInt(); + if (IHDR_type != IHDR_TYPE) { + throw new IIOException("Bad type for IHDR chunk!"); + } + + this.metadata = new PNGMetadata(); + + int width = stream.readInt(); + int height = stream.readInt(); + + // Re-use signature array to bulk-read these unsigned byte values + stream.readFully(signature, 0, 5); + int bitDepth = signature[0] & 0xff; + int colorType = signature[1] & 0xff; + int compressionMethod = signature[2] & 0xff; + int filterMethod = signature[3] & 0xff; + int interlaceMethod = signature[4] & 0xff; + + // Skip IHDR CRC + stream.skipBytes(4); + + stream.flushBefore(stream.getStreamPosition()); + + if (width == 0) { + throw new IIOException("Image width == 0!"); + } + if (height == 0) { + throw new IIOException("Image height == 0!"); + } + if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && + bitDepth != 8 && bitDepth != 16) { + throw new IIOException("Bit depth must be 1, 2, 4, 8, or 16!"); + } + if (colorType != 0 && colorType != 2 && colorType != 3 && + colorType != 4 && colorType != 6) { + throw new IIOException("Color type must be 0, 2, 3, 4, or 6!"); + } + if (colorType == PNG_COLOR_PALETTE && bitDepth == 16) { + throw new IIOException("Bad color type/bit depth combination!"); + } + if ((colorType == PNG_COLOR_RGB || + colorType == PNG_COLOR_RGB_ALPHA || + colorType == PNG_COLOR_GRAY_ALPHA) && + (bitDepth != 8 && bitDepth != 16)) { + throw new IIOException("Bad color type/bit depth combination!"); + } + if (compressionMethod != 0) { + throw new IIOException("Unknown compression method (not 0)!"); + } + if (filterMethod != 0) { + throw new IIOException("Unknown filter method (not 0)!"); + } + if (interlaceMethod != 0 && interlaceMethod != 1) { + throw new IIOException("Unknown interlace method (not 0 or 1)!"); + } + + metadata.IHDR_present = true; + metadata.IHDR_width = width; + metadata.IHDR_height = height; + metadata.IHDR_bitDepth = bitDepth; + metadata.IHDR_colorType = colorType; + metadata.IHDR_compressionMethod = compressionMethod; + metadata.IHDR_filterMethod = filterMethod; + metadata.IHDR_interlaceMethod = interlaceMethod; + gotHeader = true; + } catch (IOException e) { + throw new IIOException("I/O error reading PNG header!", e); + } + } + + private void parse_PLTE_chunk(int chunkLength) throws IOException { + if (metadata.PLTE_present) { + processWarningOccurred( +"A PNG image may not contain more than one PLTE chunk.\n" + +"The chunk wil be ignored."); + return; + } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY || + metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) { + processWarningOccurred( +"A PNG gray or gray alpha image cannot have a PLTE chunk.\n" + +"The chunk wil be ignored."); + return; + } + + byte[] palette = new byte[chunkLength]; + stream.readFully(palette); + + int numEntries = chunkLength/3; + if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) { + int maxEntries = 1 << metadata.IHDR_bitDepth; + if (numEntries > maxEntries) { + processWarningOccurred( +"PLTE chunk contains too many entries for bit depth, ignoring extras."); + numEntries = maxEntries; + } + numEntries = Math.min(numEntries, maxEntries); + } + + // Round array sizes up to 2^2^n + int paletteEntries; + if (numEntries > 16) { + paletteEntries = 256; + } else if (numEntries > 4) { + paletteEntries = 16; + } else if (numEntries > 2) { + paletteEntries = 4; + } else { + paletteEntries = 2; + } + + metadata.PLTE_present = true; + metadata.PLTE_red = new byte[paletteEntries]; + metadata.PLTE_green = new byte[paletteEntries]; + metadata.PLTE_blue = new byte[paletteEntries]; + + int index = 0; + for (int i = 0; i < numEntries; i++) { + metadata.PLTE_red[i] = palette[index++]; + metadata.PLTE_green[i] = palette[index++]; + metadata.PLTE_blue[i] = palette[index++]; + } + } + + private void parse_bKGD_chunk() throws IOException { + if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) { + metadata.bKGD_colorType = PNG_COLOR_PALETTE; + metadata.bKGD_index = stream.readUnsignedByte(); + } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY || + metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) { + metadata.bKGD_colorType = PNG_COLOR_GRAY; + metadata.bKGD_gray = stream.readUnsignedShort(); + } else { // RGB or RGB_ALPHA + metadata.bKGD_colorType = PNG_COLOR_RGB; + metadata.bKGD_red = stream.readUnsignedShort(); + metadata.bKGD_green = stream.readUnsignedShort(); + metadata.bKGD_blue = stream.readUnsignedShort(); + } + + metadata.bKGD_present = true; + } + + private void parse_cHRM_chunk() throws IOException { + metadata.cHRM_whitePointX = stream.readInt(); + metadata.cHRM_whitePointY = stream.readInt(); + metadata.cHRM_redX = stream.readInt(); + metadata.cHRM_redY = stream.readInt(); + metadata.cHRM_greenX = stream.readInt(); + metadata.cHRM_greenY = stream.readInt(); + metadata.cHRM_blueX = stream.readInt(); + metadata.cHRM_blueY = stream.readInt(); + + metadata.cHRM_present = true; + } + + private void parse_gAMA_chunk() throws IOException { + int gamma = stream.readInt(); + metadata.gAMA_gamma = gamma; + + metadata.gAMA_present = true; + } + + private void parse_hIST_chunk(int chunkLength) throws IOException, + IIOException + { + if (!metadata.PLTE_present) { + throw new IIOException("hIST chunk without prior PLTE chunk!"); + } + + /* According to PNG specification length of + * hIST chunk is specified in bytes and + * hIST chunk consists of 2 byte elements + * (so we expect length is even). + */ + metadata.hIST_histogram = new char[chunkLength/2]; + stream.readFully(metadata.hIST_histogram, + 0, metadata.hIST_histogram.length); + + metadata.hIST_present = true; + } + + private void parse_iCCP_chunk(int chunkLength) throws IOException { + String keyword = readNullTerminatedString("ISO-8859-1", 80); + metadata.iCCP_profileName = keyword; + + metadata.iCCP_compressionMethod = stream.readUnsignedByte(); + + byte[] compressedProfile = + new byte[chunkLength - keyword.length() - 2]; + stream.readFully(compressedProfile); + metadata.iCCP_compressedProfile = compressedProfile; + + metadata.iCCP_present = true; + } + + private void parse_iTXt_chunk(int chunkLength) throws IOException { + long chunkStart = stream.getStreamPosition(); + + String keyword = readNullTerminatedString("ISO-8859-1", 80); + metadata.iTXt_keyword.add(keyword); + + int compressionFlag = stream.readUnsignedByte(); + metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1)); + + int compressionMethod = stream.readUnsignedByte(); + metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); + + String languageTag = readNullTerminatedString("UTF8", 80); + metadata.iTXt_languageTag.add(languageTag); + + long pos = stream.getStreamPosition(); + int maxLen = (int)(chunkStart + chunkLength - pos); + String translatedKeyword = + readNullTerminatedString("UTF8", maxLen); + metadata.iTXt_translatedKeyword.add(translatedKeyword); + + String text; + pos = stream.getStreamPosition(); + byte[] b = new byte[(int)(chunkStart + chunkLength - pos)]; + stream.readFully(b); + + if (compressionFlag == 1) { // Decompress the text + text = new String(inflate(b), "UTF8"); + } else { + text = new String(b, "UTF8"); + } + metadata.iTXt_text.add(text); + } + + private void parse_pHYs_chunk() throws IOException { + metadata.pHYs_pixelsPerUnitXAxis = stream.readInt(); + metadata.pHYs_pixelsPerUnitYAxis = stream.readInt(); + metadata.pHYs_unitSpecifier = stream.readUnsignedByte(); + + metadata.pHYs_present = true; + } + + private void parse_sBIT_chunk() throws IOException { + int colorType = metadata.IHDR_colorType; + if (colorType == PNG_COLOR_GRAY || + colorType == PNG_COLOR_GRAY_ALPHA) { + metadata.sBIT_grayBits = stream.readUnsignedByte(); + } else if (colorType == PNG_COLOR_RGB || + colorType == PNG_COLOR_PALETTE || + colorType == PNG_COLOR_RGB_ALPHA) { + metadata.sBIT_redBits = stream.readUnsignedByte(); + metadata.sBIT_greenBits = stream.readUnsignedByte(); + metadata.sBIT_blueBits = stream.readUnsignedByte(); + } + + if (colorType == PNG_COLOR_GRAY_ALPHA || + colorType == PNG_COLOR_RGB_ALPHA) { + metadata.sBIT_alphaBits = stream.readUnsignedByte(); + } + + metadata.sBIT_colorType = colorType; + metadata.sBIT_present = true; + } + + private void parse_sPLT_chunk(int chunkLength) + throws IOException, IIOException { + metadata.sPLT_paletteName = readNullTerminatedString("ISO-8859-1", 80); + chunkLength -= metadata.sPLT_paletteName.length() + 1; + + int sampleDepth = stream.readUnsignedByte(); + metadata.sPLT_sampleDepth = sampleDepth; + + int numEntries = chunkLength/(4*(sampleDepth/8) + 2); + metadata.sPLT_red = new int[numEntries]; + metadata.sPLT_green = new int[numEntries]; + metadata.sPLT_blue = new int[numEntries]; + metadata.sPLT_alpha = new int[numEntries]; + metadata.sPLT_frequency = new int[numEntries]; + + if (sampleDepth == 8) { + for (int i = 0; i < numEntries; i++) { + metadata.sPLT_red[i] = stream.readUnsignedByte(); + metadata.sPLT_green[i] = stream.readUnsignedByte(); + metadata.sPLT_blue[i] = stream.readUnsignedByte(); + metadata.sPLT_alpha[i] = stream.readUnsignedByte(); + metadata.sPLT_frequency[i] = stream.readUnsignedShort(); + } + } else if (sampleDepth == 16) { + for (int i = 0; i < numEntries; i++) { + metadata.sPLT_red[i] = stream.readUnsignedShort(); + metadata.sPLT_green[i] = stream.readUnsignedShort(); + metadata.sPLT_blue[i] = stream.readUnsignedShort(); + metadata.sPLT_alpha[i] = stream.readUnsignedShort(); + metadata.sPLT_frequency[i] = stream.readUnsignedShort(); + } + } else { + throw new IIOException("sPLT sample depth not 8 or 16!"); + } + + metadata.sPLT_present = true; + } + + private void parse_sRGB_chunk() throws IOException { + metadata.sRGB_renderingIntent = stream.readUnsignedByte(); + + metadata.sRGB_present = true; + } + + private void parse_tEXt_chunk(int chunkLength) throws IOException { + String keyword = readNullTerminatedString("ISO-8859-1", 80); + metadata.tEXt_keyword.add(keyword); + + byte[] b = new byte[chunkLength - keyword.length() - 1]; + stream.readFully(b); + metadata.tEXt_text.add(new String(b, "ISO-8859-1")); + } + + private void parse_tIME_chunk() throws IOException { + metadata.tIME_year = stream.readUnsignedShort(); + metadata.tIME_month = stream.readUnsignedByte(); + metadata.tIME_day = stream.readUnsignedByte(); + metadata.tIME_hour = stream.readUnsignedByte(); + metadata.tIME_minute = stream.readUnsignedByte(); + metadata.tIME_second = stream.readUnsignedByte(); + + metadata.tIME_present = true; + } + + private void parse_tRNS_chunk(int chunkLength) throws IOException { + int colorType = metadata.IHDR_colorType; + if (colorType == PNG_COLOR_PALETTE) { + if (!metadata.PLTE_present) { + processWarningOccurred( +"tRNS chunk without prior PLTE chunk, ignoring it."); + return; + } + + // Alpha table may have fewer entries than RGB palette + int maxEntries = metadata.PLTE_red.length; + int numEntries = chunkLength; + if (numEntries > maxEntries) { + processWarningOccurred( +"tRNS chunk has more entries than prior PLTE chunk, ignoring extras."); + numEntries = maxEntries; + } + metadata.tRNS_alpha = new byte[numEntries]; + metadata.tRNS_colorType = PNG_COLOR_PALETTE; + stream.read(metadata.tRNS_alpha, 0, numEntries); + stream.skipBytes(chunkLength - numEntries); + } else if (colorType == PNG_COLOR_GRAY) { + if (chunkLength != 2) { + processWarningOccurred( +"tRNS chunk for gray image must have length 2, ignoring chunk."); + stream.skipBytes(chunkLength); + return; + } + metadata.tRNS_gray = stream.readUnsignedShort(); + metadata.tRNS_colorType = PNG_COLOR_GRAY; + } else if (colorType == PNG_COLOR_RGB) { + if (chunkLength != 6) { + processWarningOccurred( +"tRNS chunk for RGB image must have length 6, ignoring chunk."); + stream.skipBytes(chunkLength); + return; + } + metadata.tRNS_red = stream.readUnsignedShort(); + metadata.tRNS_green = stream.readUnsignedShort(); + metadata.tRNS_blue = stream.readUnsignedShort(); + metadata.tRNS_colorType = PNG_COLOR_RGB; + } else { + processWarningOccurred( +"Gray+Alpha and RGBS images may not have a tRNS chunk, ignoring it."); + return; + } + + metadata.tRNS_present = true; + } + + private static byte[] inflate(byte[] b) throws IOException { + InputStream bais = new ByteArrayInputStream(b); + InputStream iis = new InflaterInputStream(bais); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + int c; + try { + while ((c = iis.read()) != -1) { + baos.write(c); + } + } finally { + iis.close(); + } + return baos.toByteArray(); + } + + private void parse_zTXt_chunk(int chunkLength) throws IOException { + String keyword = readNullTerminatedString("ISO-8859-1", 80); + metadata.zTXt_keyword.add(keyword); + + int method = stream.readUnsignedByte(); + metadata.zTXt_compressionMethod.add(new Integer(method)); + + byte[] b = new byte[chunkLength - keyword.length() - 2]; + stream.readFully(b); + metadata.zTXt_text.add(new String(inflate(b), "ISO-8859-1")); + } + + private void readMetadata() throws IIOException { + if (gotMetadata) { + return; + } + + readHeader(); + + /* + * Optimization: We can skip the remaining metadata if the + * ignoreMetadata flag is set, and only if this is not a palette + * image (in that case, we need to read the metadata to get the + * tRNS chunk, which is needed for the getImageTypes() method). + */ + int colorType = metadata.IHDR_colorType; + if (ignoreMetadata && colorType != PNG_COLOR_PALETTE) { + try { + while (true) { + int chunkLength = stream.readInt(); + int chunkType = stream.readInt(); + + if (chunkType == IDAT_TYPE) { + // We've reached the image data + stream.skipBytes(-8); + imageStartPosition = stream.getStreamPosition(); + break; + } else { + // Skip the chunk plus the 4 CRC bytes that follow + stream.skipBytes(chunkLength + 4); + } + } + } catch (IOException e) { + throw new IIOException("Error skipping PNG metadata", e); + } + + gotMetadata = true; + return; + } + + try { + loop: while (true) { + int chunkLength = stream.readInt(); + int chunkType = stream.readInt(); + int chunkCRC; + + // verify the chunk length + if (chunkLength < 0) { + throw new IIOException("Invalid chunk lenght " + chunkLength); + }; + + try { + stream.mark(); + stream.seek(stream.getStreamPosition() + chunkLength); + chunkCRC = stream.readInt(); + stream.reset(); + } catch (IOException e) { + throw new IIOException("Invalid chunk length " + chunkLength); + } + + switch (chunkType) { + case IDAT_TYPE: + // If chunk type is 'IDAT', we've reached the image data. + stream.skipBytes(-8); + imageStartPosition = stream.getStreamPosition(); + break loop; + case PLTE_TYPE: + parse_PLTE_chunk(chunkLength); + break; + case bKGD_TYPE: + parse_bKGD_chunk(); + break; + case cHRM_TYPE: + parse_cHRM_chunk(); + break; + case gAMA_TYPE: + parse_gAMA_chunk(); + break; + case hIST_TYPE: + parse_hIST_chunk(chunkLength); + break; + case iCCP_TYPE: + parse_iCCP_chunk(chunkLength); + break; + case iTXt_TYPE: + parse_iTXt_chunk(chunkLength); + break; + case pHYs_TYPE: + parse_pHYs_chunk(); + break; + case sBIT_TYPE: + parse_sBIT_chunk(); + break; + case sPLT_TYPE: + parse_sPLT_chunk(chunkLength); + break; + case sRGB_TYPE: + parse_sRGB_chunk(); + break; + case tEXt_TYPE: + parse_tEXt_chunk(chunkLength); + break; + case tIME_TYPE: + parse_tIME_chunk(); + break; + case tRNS_TYPE: + parse_tRNS_chunk(chunkLength); + break; + case zTXt_TYPE: + parse_zTXt_chunk(chunkLength); + break; + default: + // Read an unknown chunk + byte[] b = new byte[chunkLength]; + stream.readFully(b); + + StringBuilder chunkName = new StringBuilder(4); + chunkName.append((char)(chunkType >>> 24)); + chunkName.append((char)((chunkType >> 16) & 0xff)); + chunkName.append((char)((chunkType >> 8) & 0xff)); + chunkName.append((char)(chunkType & 0xff)); + + int ancillaryBit = chunkType >>> 28; + if (ancillaryBit == 0) { + processWarningOccurred( +"Encountered unknown chunk with critical bit set!"); + } + + metadata.unknownChunkType.add(chunkName.toString()); + metadata.unknownChunkData.add(b); + break; + } + + // double check whether all chunk data were consumed + if (chunkCRC != stream.readInt()) { + throw new IIOException("Failed to read a chunk of type " + + chunkType); + } + stream.flushBefore(stream.getStreamPosition()); + } + } catch (IOException e) { + throw new IIOException("Error reading PNG metadata", e); + } + + gotMetadata = true; + } + + // Data filtering methods + + private static void decodeSubFilter(byte[] curr, int coff, int count, + int bpp) { + for (int i = bpp; i < count; i++) { + int val; + + val = curr[i + coff] & 0xff; + val += curr[i + coff - bpp] & 0xff; + + curr[i + coff] = (byte)val; + } + } + + private static void decodeUpFilter(byte[] curr, int coff, + byte[] prev, int poff, + int count) { + for (int i = 0; i < count; i++) { + int raw = curr[i + coff] & 0xff; + int prior = prev[i + poff] & 0xff; + + curr[i + coff] = (byte)(raw + prior); + } + } + + private static void decodeAverageFilter(byte[] curr, int coff, + byte[] prev, int poff, + int count, int bpp) { + int raw, priorPixel, priorRow; + + for (int i = 0; i < bpp; i++) { + raw = curr[i + coff] & 0xff; + priorRow = prev[i + poff] & 0xff; + + curr[i + coff] = (byte)(raw + priorRow/2); + } + + for (int i = bpp; i < count; i++) { + raw = curr[i + coff] & 0xff; + priorPixel = curr[i + coff - bpp] & 0xff; + priorRow = prev[i + poff] & 0xff; + + curr[i + coff] = (byte)(raw + (priorPixel + priorRow)/2); + } + } + + private static int paethPredictor(int a, int b, int c) { + int p = a + b - c; + int pa = Math.abs(p - a); + int pb = Math.abs(p - b); + int pc = Math.abs(p - c); + + if ((pa <= pb) && (pa <= pc)) { + return a; + } else if (pb <= pc) { + return b; + } else { + return c; + } + } + + private static void decodePaethFilter(byte[] curr, int coff, + byte[] prev, int poff, + int count, int bpp) { + int raw, priorPixel, priorRow, priorRowPixel; + + for (int i = 0; i < bpp; i++) { + raw = curr[i + coff] & 0xff; + priorRow = prev[i + poff] & 0xff; + + curr[i + coff] = (byte)(raw + priorRow); + } + + for (int i = bpp; i < count; i++) { + raw = curr[i + coff] & 0xff; + priorPixel = curr[i + coff - bpp] & 0xff; + priorRow = prev[i + poff] & 0xff; + priorRowPixel = prev[i + poff - bpp] & 0xff; + + curr[i + coff] = (byte)(raw + paethPredictor(priorPixel, + priorRow, + priorRowPixel)); + } + } + + private static final int[][] bandOffsets = { + null, + { 0 }, // G + { 0, 1 }, // GA in GA order + { 0, 1, 2 }, // RGB in RGB order + { 0, 1, 2, 3 } // RGBA in RGBA order + }; + + private WritableRaster createRaster(int width, int height, int bands, + int scanlineStride, + int bitDepth) { + + DataBuffer dataBuffer; + WritableRaster ras = null; + Point origin = new Point(0, 0); + if ((bitDepth < 8) && (bands == 1)) { + dataBuffer = new DataBufferByte(height*scanlineStride); + ras = Raster.createPackedRaster(dataBuffer, + width, height, + bitDepth, + origin); + } else if (bitDepth <= 8) { + dataBuffer = new DataBufferByte(height*scanlineStride); + ras = Raster.createInterleavedRaster(dataBuffer, + width, height, + scanlineStride, + bands, + bandOffsets[bands], + origin); + } else { + dataBuffer = new DataBufferUShort(height*scanlineStride); + ras = Raster.createInterleavedRaster(dataBuffer, + width, height, + scanlineStride, + bands, + bandOffsets[bands], + origin); + } + + return ras; + } + + private void skipPass(int passWidth, int passHeight) + throws IOException, IIOException { + if ((passWidth == 0) || (passHeight == 0)) { + return; + } + + int inputBands = inputBandsForColorType[metadata.IHDR_colorType]; + int bytesPerRow = (inputBands*passWidth*metadata.IHDR_bitDepth + 7)/8; + + // Read the image row-by-row + for (int srcY = 0; srcY < passHeight; srcY++) { + // Skip filter byte and the remaining row bytes + pixelStream.skipBytes(1 + bytesPerRow); + + // If read has been aborted, just return + // processReadAborted will be called later + if (abortRequested()) { + return; + } + } + } + + private void updateImageProgress(int newPixels) { + pixelsDone += newPixels; + processImageProgress(100.0F*pixelsDone/totalPixels); + } + + private void decodePass(int passNum, + int xStart, int yStart, + int xStep, int yStep, + int passWidth, int passHeight) throws IOException { + + if ((passWidth == 0) || (passHeight == 0)) { + return; + } + + WritableRaster imRas = theImage.getWritableTile(0, 0); + int dstMinX = imRas.getMinX(); + int dstMaxX = dstMinX + imRas.getWidth() - 1; + int dstMinY = imRas.getMinY(); + int dstMaxY = dstMinY + imRas.getHeight() - 1; + + // Determine which pixels will be updated in this pass + int[] vals = + ReaderUtil.computeUpdatedPixels(sourceRegion, + destinationOffset, + dstMinX, dstMinY, + dstMaxX, dstMaxY, + sourceXSubsampling, + sourceYSubsampling, + xStart, yStart, + passWidth, passHeight, + xStep, yStep); + int updateMinX = vals[0]; + int updateMinY = vals[1]; + int updateWidth = vals[2]; + int updateXStep = vals[4]; + int updateYStep = vals[5]; + + int bitDepth = metadata.IHDR_bitDepth; + int inputBands = inputBandsForColorType[metadata.IHDR_colorType]; + int bytesPerPixel = (bitDepth == 16) ? 2 : 1; + bytesPerPixel *= inputBands; + + int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8; + int eltsPerRow = (bitDepth == 16) ? bytesPerRow/2 : bytesPerRow; + + // If no pixels need updating, just skip the input data + if (updateWidth == 0) { + for (int srcY = 0; srcY < passHeight; srcY++) { + // Update count of pixels read + updateImageProgress(passWidth); + // Skip filter byte and the remaining row bytes + pixelStream.skipBytes(1 + bytesPerRow); + } + return; + } + + // Backwards map from destination pixels + // (dstX = updateMinX + k*updateXStep) + // to source pixels (sourceX), and then + // to offset and skip in passRow (srcX and srcXStep) + int sourceX = + (updateMinX - destinationOffset.x)*sourceXSubsampling + + sourceRegion.x; + int srcX = (sourceX - xStart)/xStep; + + // Compute the step factor in the source + int srcXStep = updateXStep*sourceXSubsampling/xStep; + + byte[] byteData = null; + short[] shortData = null; + byte[] curr = new byte[bytesPerRow]; + byte[] prior = new byte[bytesPerRow]; + + // Create a 1-row tall Raster to hold the data + WritableRaster passRow = createRaster(passWidth, 1, inputBands, + eltsPerRow, + bitDepth); + + // Create an array suitable for holding one pixel + int[] ps = passRow.getPixel(0, 0, (int[])null); + + DataBuffer dataBuffer = passRow.getDataBuffer(); + int type = dataBuffer.getDataType(); + if (type == DataBuffer.TYPE_BYTE) { + byteData = ((DataBufferByte)dataBuffer).getData(); + } else { + shortData = ((DataBufferUShort)dataBuffer).getData(); + } + + processPassStarted(theImage, + passNum, + sourceMinProgressivePass, + sourceMaxProgressivePass, + updateMinX, updateMinY, + updateXStep, updateYStep, + destinationBands); + + // Handle source and destination bands + if (sourceBands != null) { + passRow = passRow.createWritableChild(0, 0, + passRow.getWidth(), 1, + 0, 0, + sourceBands); + } + if (destinationBands != null) { + imRas = imRas.createWritableChild(0, 0, + imRas.getWidth(), + imRas.getHeight(), + 0, 0, + destinationBands); + } + + // Determine if all of the relevant output bands have the + // same bit depth as the source data + boolean adjustBitDepths = false; + int[] outputSampleSize = imRas.getSampleModel().getSampleSize(); + int numBands = outputSampleSize.length; + for (int b = 0; b < numBands; b++) { + if (outputSampleSize[b] != bitDepth) { + adjustBitDepths = true; + break; + } + } + + // If the bit depths differ, create a lookup table per band to perform + // the conversion + int[][] scale = null; + if (adjustBitDepths) { + int maxInSample = (1 << bitDepth) - 1; + int halfMaxInSample = maxInSample/2; + scale = new int[numBands][]; + for (int b = 0; b < numBands; b++) { + int maxOutSample = (1 << outputSampleSize[b]) - 1; + scale[b] = new int[maxInSample + 1]; + for (int s = 0; s <= maxInSample; s++) { + scale[b][s] = + (s*maxOutSample + halfMaxInSample)/maxInSample; + } + } + } + + // Limit passRow to relevant area for the case where we + // will can setRect to copy a contiguous span + boolean useSetRect = srcXStep == 1 && + updateXStep == 1 && + !adjustBitDepths && + (imRas instanceof ByteInterleavedRaster); + + if (useSetRect) { + passRow = passRow.createWritableChild(srcX, 0, + updateWidth, 1, + 0, 0, + null); + } + + // Decode the (sub)image row-by-row + for (int srcY = 0; srcY < passHeight; srcY++) { + // Update count of pixels read + updateImageProgress(passWidth); + + // Read the filter type byte and a row of data + int filter = pixelStream.read(); + try { + // Swap curr and prior + byte[] tmp = prior; + prior = curr; + curr = tmp; + + pixelStream.readFully(curr, 0, bytesPerRow); + } catch (java.util.zip.ZipException ze) { + // TODO - throw a more meaningful exception + throw ze; + } + + switch (filter) { + case PNG_FILTER_NONE: + break; + case PNG_FILTER_SUB: + decodeSubFilter(curr, 0, bytesPerRow, bytesPerPixel); + break; + case PNG_FILTER_UP: + decodeUpFilter(curr, 0, prior, 0, bytesPerRow); + break; + case PNG_FILTER_AVERAGE: + decodeAverageFilter(curr, 0, prior, 0, bytesPerRow, + bytesPerPixel); + break; + case PNG_FILTER_PAETH: + decodePaethFilter(curr, 0, prior, 0, bytesPerRow, + bytesPerPixel); + break; + default: + throw new IIOException("Unknown row filter type (= " + + filter + ")!"); + } + + // Copy data into passRow byte by byte + if (bitDepth < 16) { + System.arraycopy(curr, 0, byteData, 0, bytesPerRow); + } else { + int idx = 0; + for (int j = 0; j < eltsPerRow; j++) { + shortData[j] = + (short)((curr[idx] << 8) | (curr[idx + 1] & 0xff)); + idx += 2; + } + } + + // True Y position in source + int sourceY = srcY*yStep + yStart; + if ((sourceY >= sourceRegion.y) && + (sourceY < sourceRegion.y + sourceRegion.height) && + (((sourceY - sourceRegion.y) % + sourceYSubsampling) == 0)) { + + int dstY = destinationOffset.y + + (sourceY - sourceRegion.y)/sourceYSubsampling; + if (dstY < dstMinY) { + continue; + } + if (dstY > dstMaxY) { + break; + } + + if (useSetRect) { + imRas.setRect(updateMinX, dstY, passRow); + } else { + int newSrcX = srcX; + + for (int dstX = updateMinX; + dstX < updateMinX + updateWidth; + dstX += updateXStep) { + + passRow.getPixel(newSrcX, 0, ps); + if (adjustBitDepths) { + for (int b = 0; b < numBands; b++) { + ps[b] = scale[b][ps[b]]; + } + } + imRas.setPixel(dstX, dstY, ps); + newSrcX += srcXStep; + } + } + + processImageUpdate(theImage, + updateMinX, dstY, + updateWidth, 1, + updateXStep, updateYStep, + destinationBands); + + // If read has been aborted, just return + // processReadAborted will be called later + if (abortRequested()) { + return; + } + } + } + + processPassComplete(theImage); + } + + private void decodeImage() + throws IOException, IIOException { + int width = metadata.IHDR_width; + int height = metadata.IHDR_height; + + this.pixelsDone = 0; + this.totalPixels = width*height; + + clearAbortRequest(); + + if (metadata.IHDR_interlaceMethod == 0) { + decodePass(0, 0, 0, 1, 1, width, height); + } else { + for (int i = 0; i <= sourceMaxProgressivePass; i++) { + int XOffset = adam7XOffset[i]; + int YOffset = adam7YOffset[i]; + int XSubsampling = adam7XSubsampling[i]; + int YSubsampling = adam7YSubsampling[i]; + int xbump = adam7XSubsampling[i + 1] - 1; + int ybump = adam7YSubsampling[i + 1] - 1; + + if (i >= sourceMinProgressivePass) { + decodePass(i, + XOffset, + YOffset, + XSubsampling, + YSubsampling, + (width + xbump)/XSubsampling, + (height + ybump)/YSubsampling); + } else { + skipPass((width + xbump)/XSubsampling, + (height + ybump)/YSubsampling); + } + + // If read has been aborted, just return + // processReadAborted will be called later + if (abortRequested()) { + return; + } + } + } + } + + private void readImage(ImageReadParam param) throws IIOException { + readMetadata(); + + int width = metadata.IHDR_width; + int height = metadata.IHDR_height; + + // Init default values + sourceXSubsampling = 1; + sourceYSubsampling = 1; + sourceMinProgressivePass = 0; + sourceMaxProgressivePass = 6; + sourceBands = null; + destinationBands = null; + destinationOffset = new Point(0, 0); + + // If an ImageReadParam is available, get values from it + if (param != null) { + sourceXSubsampling = param.getSourceXSubsampling(); + sourceYSubsampling = param.getSourceYSubsampling(); + + sourceMinProgressivePass = + Math.max(param.getSourceMinProgressivePass(), 0); + sourceMaxProgressivePass = + Math.min(param.getSourceMaxProgressivePass(), 6); + + sourceBands = param.getSourceBands(); + destinationBands = param.getDestinationBands(); + destinationOffset = param.getDestinationOffset(); + } + Inflater inf = null; + try { + stream.seek(imageStartPosition); + + Enumeration e = new PNGImageDataEnumeration(stream); + InputStream is = new SequenceInputStream(e); + + /* InflaterInputStream uses an Inflater instance which consumes + * native (non-GC visible) resources. This is normally implicitly + * freed when the stream is closed. However since the + * InflaterInputStream wraps a client-supplied input stream, + * we cannot close it. + * But the app may depend on GC finalization to close the stream. + * Therefore to ensure timely freeing of native resources we + * explicitly create the Inflater instance and free its resources + * when we are done with the InflaterInputStream by calling + * inf.end(); + */ + inf = new Inflater(); + is = new InflaterInputStream(is, inf); + is = new BufferedInputStream(is); + this.pixelStream = new DataInputStream(is); + + /* + * NB: the PNG spec declares that valid range for width + * and height is [1, 2^31-1], so here we may fail to allocate + * a buffer for destination image due to memory limitation. + * + * However, the recovery strategy for this case should be + * defined on the level of application, so we will not + * try to estimate the required amount of the memory and/or + * handle OOM in any way. + */ + theImage = getDestination(param, + getImageTypes(0), + width, + height); + + Rectangle destRegion = new Rectangle(0, 0, 0, 0); + sourceRegion = new Rectangle(0, 0, 0, 0); + computeRegions(param, width, height, + theImage, + sourceRegion, destRegion); + destinationOffset.setLocation(destRegion.getLocation()); + + // At this point the header has been read and we know + // how many bands are in the image, so perform checking + // of the read param. + int colorType = metadata.IHDR_colorType; + checkReadParamBandSettings(param, + inputBandsForColorType[colorType], + theImage.getSampleModel().getNumBands()); + + processImageStarted(0); + decodeImage(); + if (abortRequested()) { + processReadAborted(); + } else { + processImageComplete(); + } + } catch (IOException e) { + throw new IIOException("Error reading PNG image data", e); + } finally { + if (inf != null) { + inf.end(); + } + } + } + + public int getNumImages(boolean allowSearch) throws IIOException { + if (stream == null) { + throw new IllegalStateException("No input source set!"); + } + if (seekForwardOnly && allowSearch) { + throw new IllegalStateException + ("seekForwardOnly and allowSearch can't both be true!"); + } + return 1; + } + + public int getWidth(int imageIndex) throws IIOException { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException("imageIndex != 0!"); + } + + readHeader(); + + return metadata.IHDR_width; + } + + public int getHeight(int imageIndex) throws IIOException { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException("imageIndex != 0!"); + } + + readHeader(); + + return metadata.IHDR_height; + } + + public Iterator getImageTypes(int imageIndex) + throws IIOException + { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException("imageIndex != 0!"); + } + + readHeader(); + + ArrayList l = + new ArrayList(1); + + ColorSpace rgb; + ColorSpace gray; + int[] bandOffsets; + + int bitDepth = metadata.IHDR_bitDepth; + int colorType = metadata.IHDR_colorType; + + int dataType; + if (bitDepth <= 8) { + dataType = DataBuffer.TYPE_BYTE; + } else { + dataType = DataBuffer.TYPE_USHORT; + } + + switch (colorType) { + case PNG_COLOR_GRAY: + // Packed grayscale + l.add(ImageTypeSpecifier.createGrayscale(bitDepth, + dataType, + false)); + break; + + case PNG_COLOR_RGB: + if (bitDepth == 8) { + // some standard types of buffered images + // which can be used as destination + l.add(ImageTypeSpecifier.createFromBufferedImageType( + BufferedImage.TYPE_3BYTE_BGR)); + + l.add(ImageTypeSpecifier.createFromBufferedImageType( + BufferedImage.TYPE_INT_RGB)); + + l.add(ImageTypeSpecifier.createFromBufferedImageType( + BufferedImage.TYPE_INT_BGR)); + + } + // Component R, G, B + rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB); + bandOffsets = new int[3]; + bandOffsets[0] = 0; + bandOffsets[1] = 1; + bandOffsets[2] = 2; + l.add(ImageTypeSpecifier.createInterleaved(rgb, + bandOffsets, + dataType, + false, + false)); + break; + + case PNG_COLOR_PALETTE: + readMetadata(); // Need tRNS chunk + + /* + * The PLTE chunk spec says: + * + * The number of palette entries must not exceed the range that + * can be represented in the image bit depth (for example, 2^4 = 16 + * for a bit depth of 4). It is permissible to have fewer entries + * than the bit depth would allow. In that case, any out-of-range + * pixel value found in the image data is an error. + * + * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE + * + * Consequently, the case when the palette length is smaller than + * 2^bitDepth is legal in the view of PNG spec. + * + * However the spec of createIndexed() method demands the exact + * equality of the palette lengh and number of possible palette + * entries (2^bitDepth). + * + * {@link javax.imageio.ImageTypeSpecifier.html#createIndexed} + * + * In order to avoid this contradiction we need to extend the + * palette arrays to the limit defined by the bitDepth. + */ + + int plength = 1 << bitDepth; + + byte[] red = metadata.PLTE_red; + byte[] green = metadata.PLTE_green; + byte[] blue = metadata.PLTE_blue; + + if (metadata.PLTE_red.length < plength) { + red = Arrays.copyOf(metadata.PLTE_red, plength); + Arrays.fill(red, metadata.PLTE_red.length, plength, + metadata.PLTE_red[metadata.PLTE_red.length - 1]); + + green = Arrays.copyOf(metadata.PLTE_green, plength); + Arrays.fill(green, metadata.PLTE_green.length, plength, + metadata.PLTE_green[metadata.PLTE_green.length - 1]); + + blue = Arrays.copyOf(metadata.PLTE_blue, plength); + Arrays.fill(blue, metadata.PLTE_blue.length, plength, + metadata.PLTE_blue[metadata.PLTE_blue.length - 1]); + + } + + // Alpha from tRNS chunk may have fewer entries than + // the RGB LUTs from the PLTE chunk; if so, pad with + // 255. + byte[] alpha = null; + if (metadata.tRNS_present && (metadata.tRNS_alpha != null)) { + if (metadata.tRNS_alpha.length == red.length) { + alpha = metadata.tRNS_alpha; + } else { + alpha = Arrays.copyOf(metadata.tRNS_alpha, red.length); + Arrays.fill(alpha, + metadata.tRNS_alpha.length, + red.length, (byte)255); + } + } + + l.add(ImageTypeSpecifier.createIndexed(red, green, + blue, alpha, + bitDepth, + DataBuffer.TYPE_BYTE)); + break; + + case PNG_COLOR_GRAY_ALPHA: + // Component G, A + gray = ColorSpace.getInstance(ColorSpace.CS_GRAY); + bandOffsets = new int[2]; + bandOffsets[0] = 0; + bandOffsets[1] = 1; + l.add(ImageTypeSpecifier.createInterleaved(gray, + bandOffsets, + dataType, + true, + false)); + break; + + case PNG_COLOR_RGB_ALPHA: + if (bitDepth == 8) { + // some standard types of buffered images + // wich can be used as destination + l.add(ImageTypeSpecifier.createFromBufferedImageType( + BufferedImage.TYPE_4BYTE_ABGR)); + + l.add(ImageTypeSpecifier.createFromBufferedImageType( + BufferedImage.TYPE_INT_ARGB)); + } + + // Component R, G, B, A (non-premultiplied) + rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB); + bandOffsets = new int[4]; + bandOffsets[0] = 0; + bandOffsets[1] = 1; + bandOffsets[2] = 2; + bandOffsets[3] = 3; + + l.add(ImageTypeSpecifier.createInterleaved(rgb, + bandOffsets, + dataType, + true, + false)); + break; + + default: + break; + } + + return l.iterator(); + } + + /* + * Super class implementation uses first element + * of image types list as raw image type. + * + * Also, super implementation uses first element of this list + * as default destination type image read param does not specify + * anything other. + * + * However, in case of RGB and RGBA color types, raw image type + * produces buffered image of custom type. It causes some + * performance degradation of subsequent rendering operations. + * + * To resolve this contradiction we put standard image types + * at the first positions of image types list (to produce standard + * images by default) and put raw image type (which is custom) + * at the last position of this list. + * + * After this changes we should override getRawImageType() + * to return last element of image types list. + */ + public ImageTypeSpecifier getRawImageType(int imageIndex) + throws IOException { + + Iterator types = getImageTypes(imageIndex); + ImageTypeSpecifier raw = null; + do { + raw = types.next(); + } while (types.hasNext()); + return raw; + } + + public ImageReadParam getDefaultReadParam() { + return new ImageReadParam(); + } + + public IIOMetadata getStreamMetadata() + throws IIOException { + return null; + } + + public IIOMetadata getImageMetadata(int imageIndex) throws IIOException { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException("imageIndex != 0!"); + } + readMetadata(); + return metadata; + } + + public BufferedImage read(int imageIndex, ImageReadParam param) + throws IIOException { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException("imageIndex != 0!"); + } + + readImage(param); + return theImage; + } + + public void reset() { + super.reset(); + resetStreamSettings(); + } + + private void resetStreamSettings() { + gotHeader = false; + gotMetadata = false; + metadata = null; + pixelStream = null; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageReaderSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageReaderSpi.java new file mode 100644 index 000000000..13855957e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageReaderSpi.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +import java.io.IOException; +import java.util.Locale; +import java.util.Iterator; +import javax.imageio.ImageReader; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.stream.ImageInputStream; + +public class PNGImageReaderSpi extends ImageReaderSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final String[] names = { "png", "PNG" }; + + private static final String[] suffixes = { "png" }; + + private static final String[] MIMETypes = { "image/png", "image/x-png" }; + + private static final String readerClassName = + "com.sun.imageio.plugins.png.PNGImageReader"; + + private static final String[] writerSpiNames = { + "com.sun.imageio.plugins.png.PNGImageWriterSpi" + }; + + public PNGImageReaderSpi() { + super(vendorName, + version, + names, + suffixes, + MIMETypes, + readerClassName, + new Class[] { ImageInputStream.class }, + writerSpiNames, + false, + null, null, + null, null, + true, + PNGMetadata.nativeMetadataFormatName, + "com.sun.imageio.plugins.png.PNGMetadataFormat", + null, null + ); + } + + public String getDescription(Locale locale) { + return "Standard PNG image reader"; + } + + public boolean canDecodeInput(Object input) throws IOException { + if (!(input instanceof ImageInputStream)) { + return false; + } + + ImageInputStream stream = (ImageInputStream)input; + byte[] b = new byte[8]; + stream.mark(); + stream.readFully(b); + stream.reset(); + + return (b[0] == (byte)137 && + b[1] == (byte)80 && + b[2] == (byte)78 && + b[3] == (byte)71 && + b[4] == (byte)13 && + b[5] == (byte)10 && + b[6] == (byte)26 && + b[7] == (byte)10); + } + + public ImageReader createReaderInstance(Object extension) { + return new PNGImageReader(this); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageWriter.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageWriter.java new file mode 100644 index 000000000..746d5b4d9 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageWriter.java @@ -0,0 +1,1188 @@ +/* + * Copyright (c) 2000, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +import java.awt.Rectangle; +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.Locale; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.ImageOutputStreamImpl; + +class CRC { + + private static int[] crcTable = new int[256]; + private int crc = 0xffffffff; + + static { + // Initialize CRC table + for (int n = 0; n < 256; n++) { + int c = n; + for (int k = 0; k < 8; k++) { + if ((c & 1) == 1) { + c = 0xedb88320 ^ (c >>> 1); + } else { + c >>>= 1; + } + + crcTable[n] = c; + } + } + } + + public CRC() {} + + public void reset() { + crc = 0xffffffff; + } + + public void update(byte[] data, int off, int len) { + for (int n = 0; n < len; n++) { + crc = crcTable[(crc ^ data[off + n]) & 0xff] ^ (crc >>> 8); + } + } + + public void update(int data) { + crc = crcTable[(crc ^ data) & 0xff] ^ (crc >>> 8); + } + + public int getValue() { + return crc ^ 0xffffffff; + } +} + + +final class ChunkStream extends ImageOutputStreamImpl { + + private ImageOutputStream stream; + private long startPos; + private CRC crc = new CRC(); + + public ChunkStream(int type, ImageOutputStream stream) throws IOException { + this.stream = stream; + this.startPos = stream.getStreamPosition(); + + stream.writeInt(-1); // length, will backpatch + writeInt(type); + } + + @Override + public int read() throws IOException { + throw new RuntimeException("Method not available"); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + throw new RuntimeException("Method not available"); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + crc.update(b, off, len); + stream.write(b, off, len); + } + + @Override + public void write(int b) throws IOException { + crc.update(b); + stream.write(b); + } + + public void finish() throws IOException { + // Write CRC + stream.writeInt(crc.getValue()); + + // Write length + long pos = stream.getStreamPosition(); + stream.seek(startPos); + stream.writeInt((int)(pos - startPos) - 12); + + // Return to end of chunk and flush to minimize buffering + stream.seek(pos); + stream.flushBefore(pos); + } + + @Override + protected void finalize() throws Throwable { + // Empty finalizer (for improved performance; no need to call + // super.finalize() in this case) + } +} + +// Compress output and write as a series of 'IDAT' chunks of +// fixed length. +final class IDATOutputStream extends ImageOutputStreamImpl { + + private static byte[] chunkType = { + (byte)'I', (byte)'D', (byte)'A', (byte)'T' + }; + + private ImageOutputStream stream; + private int chunkLength; + private long startPos; + private CRC crc = new CRC(); + + Deflater def = new Deflater(Deflater.BEST_COMPRESSION); + byte[] buf = new byte[512]; + + private int bytesRemaining; + + public IDATOutputStream(ImageOutputStream stream, int chunkLength) + throws IOException { + this.stream = stream; + this.chunkLength = chunkLength; + startChunk(); + } + + private void startChunk() throws IOException { + crc.reset(); + this.startPos = stream.getStreamPosition(); + stream.writeInt(-1); // length, will backpatch + + crc.update(chunkType, 0, 4); + stream.write(chunkType, 0, 4); + + this.bytesRemaining = chunkLength; + } + + private void finishChunk() throws IOException { + // Write CRC + stream.writeInt(crc.getValue()); + + // Write length + long pos = stream.getStreamPosition(); + stream.seek(startPos); + stream.writeInt((int)(pos - startPos) - 12); + + // Return to end of chunk and flush to minimize buffering + stream.seek(pos); + stream.flushBefore(pos); + } + + @Override + public int read() throws IOException { + throw new RuntimeException("Method not available"); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + throw new RuntimeException("Method not available"); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return; + } + + if (!def.finished()) { + def.setInput(b, off, len); + while (!def.needsInput()) { + deflate(); + } + } + } + + public void deflate() throws IOException { + int len = def.deflate(buf, 0, buf.length); + int off = 0; + + while (len > 0) { + if (bytesRemaining == 0) { + finishChunk(); + startChunk(); + } + + int nbytes = Math.min(len, bytesRemaining); + crc.update(buf, off, nbytes); + stream.write(buf, off, nbytes); + + off += nbytes; + len -= nbytes; + bytesRemaining -= nbytes; + } + } + + @Override + public void write(int b) throws IOException { + byte[] wbuf = new byte[1]; + wbuf[0] = (byte)b; + write(wbuf, 0, 1); + } + + public void finish() throws IOException { + try { + if (!def.finished()) { + def.finish(); + while (!def.finished()) { + deflate(); + } + } + finishChunk(); + } finally { + def.end(); + } + } + + @Override + protected void finalize() throws Throwable { + // Empty finalizer (for improved performance; no need to call + // super.finalize() in this case) + } +} + + +class PNGImageWriteParam extends ImageWriteParam { + + public PNGImageWriteParam(Locale locale) { + super(); + this.canWriteProgressive = true; + this.locale = locale; + } +} + +/** + */ +public class PNGImageWriter extends ImageWriter { + + ImageOutputStream stream = null; + + PNGMetadata metadata = null; + + // Factors from the ImageWriteParam + int sourceXOffset = 0; + int sourceYOffset = 0; + int sourceWidth = 0; + int sourceHeight = 0; + int[] sourceBands = null; + int periodX = 1; + int periodY = 1; + + int numBands; + int bpp; + + RowFilter rowFilter = new RowFilter(); + byte[] prevRow = null; + byte[] currRow = null; + byte[][] filteredRows = null; + + // Per-band scaling tables + // + // After the first call to initializeScaleTables, either scale and scale0 + // will be valid, or scaleh and scalel will be valid, but not both. + // + // The tables will be designed for use with a set of input but depths + // given by sampleSize, and an output bit depth given by scalingBitDepth. + // + int[] sampleSize = null; // Sample size per band, in bits + int scalingBitDepth = -1; // Output bit depth of the scaling tables + + // Tables for 1, 2, 4, or 8 bit output + byte[][] scale = null; // 8 bit table + byte[] scale0 = null; // equivalent to scale[0] + + // Tables for 16 bit output + byte[][] scaleh = null; // High bytes of output + byte[][] scalel = null; // Low bytes of output + + int totalPixels; // Total number of pixels to be written by write_IDAT + int pixelsDone; // Running count of pixels written by write_IDAT + + public PNGImageWriter(ImageWriterSpi originatingProvider) { + super(originatingProvider); + } + + @Override + public void setOutput(Object output) { + super.setOutput(output); + if (output != null) { + if (!(output instanceof ImageOutputStream)) { + throw new IllegalArgumentException("output not an ImageOutputStream!"); + } + this.stream = (ImageOutputStream)output; + } else { + this.stream = null; + } + } + + private static int[] allowedProgressivePasses = { 1, 7 }; + + @Override + public ImageWriteParam getDefaultWriteParam() { + return new PNGImageWriteParam(getLocale()); + } + + @Override + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param) { + PNGMetadata m = new PNGMetadata(); + m.initialize(imageType, imageType.getSampleModel().getNumBands()); + return m; + } + + @Override + public IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param) { + return null; + } + + @Override + public IIOMetadata convertImageMetadata(IIOMetadata inData, + ImageTypeSpecifier imageType, + ImageWriteParam param) { + // TODO - deal with imageType + if (inData instanceof PNGMetadata) { + return (PNGMetadata)((PNGMetadata)inData).clone(); + } else { + return new PNGMetadata(inData); + } + } + + private void write_magic() throws IOException { + // Write signature + byte[] magic = { (byte)137, 80, 78, 71, 13, 10, 26, 10 }; + stream.write(magic); + } + + private void write_IHDR() throws IOException { + // Write IHDR chunk + ChunkStream cs = new ChunkStream(PNGImageReader.IHDR_TYPE, stream); + cs.writeInt(metadata.IHDR_width); + cs.writeInt(metadata.IHDR_height); + cs.writeByte(metadata.IHDR_bitDepth); + cs.writeByte(metadata.IHDR_colorType); + if (metadata.IHDR_compressionMethod != 0) { + throw new IIOException( +"Only compression method 0 is defined in PNG 1.1"); + } + cs.writeByte(metadata.IHDR_compressionMethod); + if (metadata.IHDR_filterMethod != 0) { + throw new IIOException( +"Only filter method 0 is defined in PNG 1.1"); + } + cs.writeByte(metadata.IHDR_filterMethod); + if (metadata.IHDR_interlaceMethod < 0 || + metadata.IHDR_interlaceMethod > 1) { + throw new IIOException( +"Only interlace methods 0 (node) and 1 (adam7) are defined in PNG 1.1"); + } + cs.writeByte(metadata.IHDR_interlaceMethod); + cs.finish(); + } + + private void write_cHRM() throws IOException { + if (metadata.cHRM_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.cHRM_TYPE, stream); + cs.writeInt(metadata.cHRM_whitePointX); + cs.writeInt(metadata.cHRM_whitePointY); + cs.writeInt(metadata.cHRM_redX); + cs.writeInt(metadata.cHRM_redY); + cs.writeInt(metadata.cHRM_greenX); + cs.writeInt(metadata.cHRM_greenY); + cs.writeInt(metadata.cHRM_blueX); + cs.writeInt(metadata.cHRM_blueY); + cs.finish(); + } + } + + private void write_gAMA() throws IOException { + if (metadata.gAMA_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.gAMA_TYPE, stream); + cs.writeInt(metadata.gAMA_gamma); + cs.finish(); + } + } + + private void write_iCCP() throws IOException { + if (metadata.iCCP_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.iCCP_TYPE, stream); + cs.writeBytes(metadata.iCCP_profileName); + cs.writeByte(0); // null terminator + + cs.writeByte(metadata.iCCP_compressionMethod); + cs.write(metadata.iCCP_compressedProfile); + cs.finish(); + } + } + + private void write_sBIT() throws IOException { + if (metadata.sBIT_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.sBIT_TYPE, stream); + int colorType = metadata.IHDR_colorType; + if (metadata.sBIT_colorType != colorType) { + processWarningOccurred(0, +"sBIT metadata has wrong color type.\n" + +"The chunk will not be written."); + return; + } + + if (colorType == PNGImageReader.PNG_COLOR_GRAY || + colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + cs.writeByte(metadata.sBIT_grayBits); + } else if (colorType == PNGImageReader.PNG_COLOR_RGB || + colorType == PNGImageReader.PNG_COLOR_PALETTE || + colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { + cs.writeByte(metadata.sBIT_redBits); + cs.writeByte(metadata.sBIT_greenBits); + cs.writeByte(metadata.sBIT_blueBits); + } + + if (colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA || + colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { + cs.writeByte(metadata.sBIT_alphaBits); + } + cs.finish(); + } + } + + private void write_sRGB() throws IOException { + if (metadata.sRGB_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.sRGB_TYPE, stream); + cs.writeByte(metadata.sRGB_renderingIntent); + cs.finish(); + } + } + + private void write_PLTE() throws IOException { + if (metadata.PLTE_present) { + if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY || + metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + // PLTE cannot occur in a gray image + + processWarningOccurred(0, +"A PLTE chunk may not appear in a gray or gray alpha image.\n" + +"The chunk will not be written"); + return; + } + + ChunkStream cs = new ChunkStream(PNGImageReader.PLTE_TYPE, stream); + + int numEntries = metadata.PLTE_red.length; + byte[] palette = new byte[numEntries*3]; + int index = 0; + for (int i = 0; i < numEntries; i++) { + palette[index++] = metadata.PLTE_red[i]; + palette[index++] = metadata.PLTE_green[i]; + palette[index++] = metadata.PLTE_blue[i]; + } + + cs.write(palette); + cs.finish(); + } + } + + private void write_hIST() throws IOException, IIOException { + if (metadata.hIST_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.hIST_TYPE, stream); + + if (!metadata.PLTE_present) { + throw new IIOException("hIST chunk without PLTE chunk!"); + } + + cs.writeChars(metadata.hIST_histogram, + 0, metadata.hIST_histogram.length); + cs.finish(); + } + } + + private void write_tRNS() throws IOException, IIOException { + if (metadata.tRNS_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.tRNS_TYPE, stream); + int colorType = metadata.IHDR_colorType; + int chunkType = metadata.tRNS_colorType; + + // Special case: image is RGB and chunk is Gray + // Promote chunk contents to RGB + int chunkRed = metadata.tRNS_red; + int chunkGreen = metadata.tRNS_green; + int chunkBlue = metadata.tRNS_blue; + if (colorType == PNGImageReader.PNG_COLOR_RGB && + chunkType == PNGImageReader.PNG_COLOR_GRAY) { + chunkType = colorType; + chunkRed = chunkGreen = chunkBlue = + metadata.tRNS_gray; + } + + if (chunkType != colorType) { + processWarningOccurred(0, +"tRNS metadata has incompatible color type.\n" + +"The chunk will not be written."); + return; + } + + if (colorType == PNGImageReader.PNG_COLOR_PALETTE) { + if (!metadata.PLTE_present) { + throw new IIOException("tRNS chunk without PLTE chunk!"); + } + cs.write(metadata.tRNS_alpha); + } else if (colorType == PNGImageReader.PNG_COLOR_GRAY) { + cs.writeShort(metadata.tRNS_gray); + } else if (colorType == PNGImageReader.PNG_COLOR_RGB) { + cs.writeShort(chunkRed); + cs.writeShort(chunkGreen); + cs.writeShort(chunkBlue); + } else { + throw new IIOException("tRNS chunk for color type 4 or 6!"); + } + cs.finish(); + } + } + + private void write_bKGD() throws IOException { + if (metadata.bKGD_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.bKGD_TYPE, stream); + int colorType = metadata.IHDR_colorType & 0x3; + int chunkType = metadata.bKGD_colorType; + + // Special case: image is RGB(A) and chunk is Gray + // Promote chunk contents to RGB + int chunkRed = metadata.bKGD_red; + int chunkGreen = metadata.bKGD_red; + int chunkBlue = metadata.bKGD_red; + if (colorType == PNGImageReader.PNG_COLOR_RGB && + chunkType == PNGImageReader.PNG_COLOR_GRAY) { + // Make a gray bKGD chunk look like RGB + chunkType = colorType; + chunkRed = chunkGreen = chunkBlue = + metadata.bKGD_gray; + } + + // Ignore status of alpha in colorType + if (chunkType != colorType) { + processWarningOccurred(0, +"bKGD metadata has incompatible color type.\n" + +"The chunk will not be written."); + return; + } + + if (colorType == PNGImageReader.PNG_COLOR_PALETTE) { + cs.writeByte(metadata.bKGD_index); + } else if (colorType == PNGImageReader.PNG_COLOR_GRAY || + colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + cs.writeShort(metadata.bKGD_gray); + } else { // colorType == PNGImageReader.PNG_COLOR_RGB || + // colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA + cs.writeShort(chunkRed); + cs.writeShort(chunkGreen); + cs.writeShort(chunkBlue); + } + cs.finish(); + } + } + + private void write_pHYs() throws IOException { + if (metadata.pHYs_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.pHYs_TYPE, stream); + cs.writeInt(metadata.pHYs_pixelsPerUnitXAxis); + cs.writeInt(metadata.pHYs_pixelsPerUnitYAxis); + cs.writeByte(metadata.pHYs_unitSpecifier); + cs.finish(); + } + } + + private void write_sPLT() throws IOException { + if (metadata.sPLT_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.sPLT_TYPE, stream); + + cs.writeBytes(metadata.sPLT_paletteName); + cs.writeByte(0); // null terminator + + cs.writeByte(metadata.sPLT_sampleDepth); + int numEntries = metadata.sPLT_red.length; + + if (metadata.sPLT_sampleDepth == 8) { + for (int i = 0; i < numEntries; i++) { + cs.writeByte(metadata.sPLT_red[i]); + cs.writeByte(metadata.sPLT_green[i]); + cs.writeByte(metadata.sPLT_blue[i]); + cs.writeByte(metadata.sPLT_alpha[i]); + cs.writeShort(metadata.sPLT_frequency[i]); + } + } else { // sampleDepth == 16 + for (int i = 0; i < numEntries; i++) { + cs.writeShort(metadata.sPLT_red[i]); + cs.writeShort(metadata.sPLT_green[i]); + cs.writeShort(metadata.sPLT_blue[i]); + cs.writeShort(metadata.sPLT_alpha[i]); + cs.writeShort(metadata.sPLT_frequency[i]); + } + } + cs.finish(); + } + } + + private void write_tIME() throws IOException { + if (metadata.tIME_present) { + ChunkStream cs = new ChunkStream(PNGImageReader.tIME_TYPE, stream); + cs.writeShort(metadata.tIME_year); + cs.writeByte(metadata.tIME_month); + cs.writeByte(metadata.tIME_day); + cs.writeByte(metadata.tIME_hour); + cs.writeByte(metadata.tIME_minute); + cs.writeByte(metadata.tIME_second); + cs.finish(); + } + } + + private void write_tEXt() throws IOException { + Iterator keywordIter = metadata.tEXt_keyword.iterator(); + Iterator textIter = metadata.tEXt_text.iterator(); + + while (keywordIter.hasNext()) { + ChunkStream cs = new ChunkStream(PNGImageReader.tEXt_TYPE, stream); + String keyword = (String)keywordIter.next(); + cs.writeBytes(keyword); + cs.writeByte(0); + + String text = (String)textIter.next(); + cs.writeBytes(text); + cs.finish(); + } + } + + private byte[] deflate(byte[] b) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DeflaterOutputStream dos = new DeflaterOutputStream(baos); + dos.write(b); + dos.close(); + return baos.toByteArray(); + } + + private void write_iTXt() throws IOException { + Iterator keywordIter = metadata.iTXt_keyword.iterator(); + Iterator flagIter = metadata.iTXt_compressionFlag.iterator(); + Iterator methodIter = metadata.iTXt_compressionMethod.iterator(); + Iterator languageIter = metadata.iTXt_languageTag.iterator(); + Iterator translatedKeywordIter = + metadata.iTXt_translatedKeyword.iterator(); + Iterator textIter = metadata.iTXt_text.iterator(); + + while (keywordIter.hasNext()) { + ChunkStream cs = new ChunkStream(PNGImageReader.iTXt_TYPE, stream); + + cs.writeBytes(keywordIter.next()); + cs.writeByte(0); + + Boolean compressed = flagIter.next(); + cs.writeByte(compressed ? 1 : 0); + + cs.writeByte(methodIter.next().intValue()); + + cs.writeBytes(languageIter.next()); + cs.writeByte(0); + + + cs.write(translatedKeywordIter.next().getBytes("UTF8")); + cs.writeByte(0); + + String text = textIter.next(); + if (compressed) { + cs.write(deflate(text.getBytes("UTF8"))); + } else { + cs.write(text.getBytes("UTF8")); + } + cs.finish(); + } + } + + private void write_zTXt() throws IOException { + Iterator keywordIter = metadata.zTXt_keyword.iterator(); + Iterator methodIter = metadata.zTXt_compressionMethod.iterator(); + Iterator textIter = metadata.zTXt_text.iterator(); + + while (keywordIter.hasNext()) { + ChunkStream cs = new ChunkStream(PNGImageReader.zTXt_TYPE, stream); + String keyword = (String)keywordIter.next(); + cs.writeBytes(keyword); + cs.writeByte(0); + + int compressionMethod = ((Integer)methodIter.next()).intValue(); + cs.writeByte(compressionMethod); + + String text = (String)textIter.next(); + cs.write(deflate(text.getBytes("ISO-8859-1"))); + cs.finish(); + } + } + + private void writeUnknownChunks() throws IOException { + Iterator typeIter = metadata.unknownChunkType.iterator(); + Iterator dataIter = metadata.unknownChunkData.iterator(); + + while (typeIter.hasNext() && dataIter.hasNext()) { + String type = (String)typeIter.next(); + ChunkStream cs = new ChunkStream(chunkType(type), stream); + byte[] data = (byte[])dataIter.next(); + cs.write(data); + cs.finish(); + } + } + + private static int chunkType(String typeString) { + char c0 = typeString.charAt(0); + char c1 = typeString.charAt(1); + char c2 = typeString.charAt(2); + char c3 = typeString.charAt(3); + + int type = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3; + return type; + } + + private void encodePass(ImageOutputStream os, + RenderedImage image, + int xOffset, int yOffset, + int xSkip, int ySkip) throws IOException { + int minX = sourceXOffset; + int minY = sourceYOffset; + int width = sourceWidth; + int height = sourceHeight; + + // Adjust offsets and skips based on source subsampling factors + xOffset *= periodX; + xSkip *= periodX; + yOffset *= periodY; + ySkip *= periodY; + + // Early exit if no data for this pass + int hpixels = (width - xOffset + xSkip - 1)/xSkip; + int vpixels = (height - yOffset + ySkip - 1)/ySkip; + if (hpixels == 0 || vpixels == 0) { + return; + } + + // Convert X offset and skip from pixels to samples + xOffset *= numBands; + xSkip *= numBands; + + // Create row buffers + int samplesPerByte = 8/metadata.IHDR_bitDepth; + int numSamples = width*numBands; + int[] samples = new int[numSamples]; + + int bytesPerRow = hpixels*numBands; + if (metadata.IHDR_bitDepth < 8) { + bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte; + } else if (metadata.IHDR_bitDepth == 16) { + bytesPerRow *= 2; + } + + IndexColorModel icm_gray_alpha = null; + if (metadata.IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA && + image.getColorModel() instanceof IndexColorModel) + { + // reserve space for alpha samples + bytesPerRow *= 2; + + // will be used to calculate alpha value for the pixel + icm_gray_alpha = (IndexColorModel)image.getColorModel(); + } + + currRow = new byte[bytesPerRow + bpp]; + prevRow = new byte[bytesPerRow + bpp]; + filteredRows = new byte[5][bytesPerRow + bpp]; + + int bitDepth = metadata.IHDR_bitDepth; + for (int row = minY + yOffset; row < minY + height; row += ySkip) { + Rectangle rect = new Rectangle(minX, row, width, 1); + Raster ras = image.getData(rect); + if (sourceBands != null) { + ras = ras.createChild(minX, row, width, 1, minX, row, + sourceBands); + } + + ras.getPixels(minX, row, width, 1, samples); + + if (image.getColorModel().isAlphaPremultiplied()) { + WritableRaster wr = ras.createCompatibleWritableRaster(); + wr.setPixels(wr.getMinX(), wr.getMinY(), + wr.getWidth(), wr.getHeight(), + samples); + + image.getColorModel().coerceData(wr, false); + wr.getPixels(wr.getMinX(), wr.getMinY(), + wr.getWidth(), wr.getHeight(), + samples); + } + + // Reorder palette data if necessary + int[] paletteOrder = metadata.PLTE_order; + if (paletteOrder != null) { + for (int i = 0; i < numSamples; i++) { + samples[i] = paletteOrder[samples[i]]; + } + } + + int count = bpp; // leave first 'bpp' bytes zero + int pos = 0; + int tmp = 0; + + switch (bitDepth) { + case 1: case 2: case 4: + // Image can only have a single band + + int mask = samplesPerByte - 1; + for (int s = xOffset; s < numSamples; s += xSkip) { + byte val = scale0[samples[s]]; + tmp = (tmp << bitDepth) | val; + + if ((pos++ & mask) == mask) { + currRow[count++] = (byte)tmp; + tmp = 0; + pos = 0; + } + } + + // Left shift the last byte + if ((pos & mask) != 0) { + tmp <<= ((8/bitDepth) - pos)*bitDepth; + currRow[count++] = (byte)tmp; + } + break; + + case 8: + if (numBands == 1) { + for (int s = xOffset; s < numSamples; s += xSkip) { + currRow[count++] = scale0[samples[s]]; + if (icm_gray_alpha != null) { + currRow[count++] = + scale0[icm_gray_alpha.getAlpha(0xff & samples[s])]; + } + } + } else { + for (int s = xOffset; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + currRow[count++] = scale[b][samples[s + b]]; + } + } + } + break; + + case 16: + for (int s = xOffset; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + currRow[count++] = scaleh[b][samples[s + b]]; + currRow[count++] = scalel[b][samples[s + b]]; + } + } + break; + } + + // Perform filtering + int filterType = rowFilter.filterRow(metadata.IHDR_colorType, + currRow, prevRow, + filteredRows, + bytesPerRow, bpp); + + os.write(filterType); + os.write(filteredRows[filterType], bpp, bytesPerRow); + + // Swap current and previous rows + byte[] swap = currRow; + currRow = prevRow; + prevRow = swap; + + pixelsDone += hpixels; + processImageProgress(100.0F*pixelsDone/totalPixels); + + // If write has been aborted, just return; + // processWriteAborted will be called later + if (abortRequested()) { + return; + } + } + } + + // Use sourceXOffset, etc. + private void write_IDAT(RenderedImage image) throws IOException { + IDATOutputStream ios = new IDATOutputStream(stream, 32768); + try { + if (metadata.IHDR_interlaceMethod == 1) { + for (int i = 0; i < 7; i++) { + encodePass(ios, image, + PNGImageReader.adam7XOffset[i], + PNGImageReader.adam7YOffset[i], + PNGImageReader.adam7XSubsampling[i], + PNGImageReader.adam7YSubsampling[i]); + if (abortRequested()) { + break; + } + } + } else { + encodePass(ios, image, 0, 0, 1, 1); + } + } finally { + ios.finish(); + } + } + + private void writeIEND() throws IOException { + ChunkStream cs = new ChunkStream(PNGImageReader.IEND_TYPE, stream); + cs.finish(); + } + + // Check two int arrays for value equality, always returns false + // if either array is null + private boolean equals(int[] s0, int[] s1) { + if (s0 == null || s1 == null) { + return false; + } + if (s0.length != s1.length) { + return false; + } + for (int i = 0; i < s0.length; i++) { + if (s0[i] != s1[i]) { + return false; + } + } + return true; + } + + // Initialize the scale/scale0 or scaleh/scalel arrays to + // hold the results of scaling an input value to the desired + // output bit depth + private void initializeScaleTables(int[] sampleSize) { + int bitDepth = metadata.IHDR_bitDepth; + + // If the existing tables are still valid, just return + if (bitDepth == scalingBitDepth && + equals(sampleSize, this.sampleSize)) { + return; + } + + // Compute new tables + this.sampleSize = sampleSize; + this.scalingBitDepth = bitDepth; + int maxOutSample = (1 << bitDepth) - 1; + if (bitDepth <= 8) { + scale = new byte[numBands][]; + for (int b = 0; b < numBands; b++) { + int maxInSample = (1 << sampleSize[b]) - 1; + int halfMaxInSample = maxInSample/2; + scale[b] = new byte[maxInSample + 1]; + for (int s = 0; s <= maxInSample; s++) { + scale[b][s] = + (byte)((s*maxOutSample + halfMaxInSample)/maxInSample); + } + } + scale0 = scale[0]; + scaleh = scalel = null; + } else { // bitDepth == 16 + // Divide scaling table into high and low bytes + scaleh = new byte[numBands][]; + scalel = new byte[numBands][]; + + for (int b = 0; b < numBands; b++) { + int maxInSample = (1 << sampleSize[b]) - 1; + int halfMaxInSample = maxInSample/2; + scaleh[b] = new byte[maxInSample + 1]; + scalel[b] = new byte[maxInSample + 1]; + for (int s = 0; s <= maxInSample; s++) { + int val = (s*maxOutSample + halfMaxInSample)/maxInSample; + scaleh[b][s] = (byte)(val >> 8); + scalel[b][s] = (byte)(val & 0xff); + } + } + scale = null; + scale0 = null; + } + } + + @Override + public void write(IIOMetadata streamMetadata, + IIOImage image, + ImageWriteParam param) throws IIOException { + if (stream == null) { + throw new IllegalStateException("output == null!"); + } + if (image == null) { + throw new IllegalArgumentException("image == null!"); + } + if (image.hasRaster()) { + throw new UnsupportedOperationException("image has a Raster!"); + } + + RenderedImage im = image.getRenderedImage(); + SampleModel sampleModel = im.getSampleModel(); + this.numBands = sampleModel.getNumBands(); + + // Set source region and subsampling to default values + this.sourceXOffset = im.getMinX(); + this.sourceYOffset = im.getMinY(); + this.sourceWidth = im.getWidth(); + this.sourceHeight = im.getHeight(); + this.sourceBands = null; + this.periodX = 1; + this.periodY = 1; + + if (param != null) { + // Get source region and subsampling factors + Rectangle sourceRegion = param.getSourceRegion(); + if (sourceRegion != null) { + Rectangle imageBounds = new Rectangle(im.getMinX(), + im.getMinY(), + im.getWidth(), + im.getHeight()); + // Clip to actual image bounds + sourceRegion = sourceRegion.intersection(imageBounds); + sourceXOffset = sourceRegion.x; + sourceYOffset = sourceRegion.y; + sourceWidth = sourceRegion.width; + sourceHeight = sourceRegion.height; + } + + // Adjust for subsampling offsets + int gridX = param.getSubsamplingXOffset(); + int gridY = param.getSubsamplingYOffset(); + sourceXOffset += gridX; + sourceYOffset += gridY; + sourceWidth -= gridX; + sourceHeight -= gridY; + + // Get subsampling factors + periodX = param.getSourceXSubsampling(); + periodY = param.getSourceYSubsampling(); + + int[] sBands = param.getSourceBands(); + if (sBands != null) { + sourceBands = sBands; + numBands = sourceBands.length; + } + } + + // Compute output dimensions + int destWidth = (sourceWidth + periodX - 1)/periodX; + int destHeight = (sourceHeight + periodY - 1)/periodY; + if (destWidth <= 0 || destHeight <= 0) { + throw new IllegalArgumentException("Empty source region!"); + } + + // Compute total number of pixels for progress notification + this.totalPixels = destWidth*destHeight; + this.pixelsDone = 0; + + // Create metadata + IIOMetadata imd = image.getMetadata(); + if (imd != null) { + metadata = (PNGMetadata)convertImageMetadata(imd, + ImageTypeSpecifier.createFromRenderedImage(im), + null); + } else { + metadata = new PNGMetadata(); + } + + if (param != null) { + // Use Adam7 interlacing if set in write param + switch (param.getProgressiveMode()) { + case ImageWriteParam.MODE_DEFAULT: + metadata.IHDR_interlaceMethod = 1; + break; + case ImageWriteParam.MODE_DISABLED: + metadata.IHDR_interlaceMethod = 0; + break; + // MODE_COPY_FROM_METADATA should alreay be taken care of + // MODE_EXPLICIT is not allowed + } + } + + // Initialize bitDepth and colorType + metadata.initialize(new ImageTypeSpecifier(im), numBands); + + // Overwrite IHDR width and height values with values from image + metadata.IHDR_width = destWidth; + metadata.IHDR_height = destHeight; + + this.bpp = numBands*((metadata.IHDR_bitDepth == 16) ? 2 : 1); + + // Initialize scaling tables for this image + initializeScaleTables(sampleModel.getSampleSize()); + + clearAbortRequest(); + + processImageStarted(0); + + try { + write_magic(); + write_IHDR(); + + write_cHRM(); + write_gAMA(); + write_iCCP(); + write_sBIT(); + write_sRGB(); + + write_PLTE(); + + write_hIST(); + write_tRNS(); + write_bKGD(); + + write_pHYs(); + write_sPLT(); + write_tIME(); + write_tEXt(); + write_iTXt(); + write_zTXt(); + + writeUnknownChunks(); + + write_IDAT(im); + + if (abortRequested()) { + processWriteAborted(); + } else { + // Finish up and inform the listeners we are done + writeIEND(); + processImageComplete(); + } + } catch (IOException e) { + throw new IIOException("I/O error writing PNG file!", e); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageWriterSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageWriterSpi.java new file mode 100644 index 000000000..18d761423 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGImageWriterSpi.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.SampleModel; +import java.util.Locale; +import javax.imageio.ImageWriter; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; + +public class PNGImageWriterSpi extends ImageWriterSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final String[] names = { "png", "PNG" }; + + private static final String[] suffixes = { "png" }; + + private static final String[] MIMETypes = { "image/png", "image/x-png" }; + + private static final String writerClassName = + "com.sun.imageio.plugins.png.PNGImageWriter"; + + private static final String[] readerSpiNames = { + "com.sun.imageio.plugins.png.PNGImageReaderSpi" + }; + + public PNGImageWriterSpi() { + super(vendorName, + version, + names, + suffixes, + MIMETypes, + writerClassName, + new Class[] { ImageOutputStream.class }, + readerSpiNames, + false, + null, null, + null, null, + true, + PNGMetadata.nativeMetadataFormatName, + "com.sun.imageio.plugins.png.PNGMetadataFormat", + null, null + ); + } + + public boolean canEncodeImage(ImageTypeSpecifier type) { + SampleModel sampleModel = type.getSampleModel(); + ColorModel colorModel = type.getColorModel(); + + // Find the maximum bit depth across all channels + int[] sampleSize = sampleModel.getSampleSize(); + int bitDepth = sampleSize[0]; + for (int i = 1; i < sampleSize.length; i++) { + if (sampleSize[i] > bitDepth) { + bitDepth = sampleSize[i]; + } + } + + // Ensure bitDepth is between 1 and 16 + if (bitDepth < 1 || bitDepth > 16) { + return false; + } + + // Check number of bands, alpha + int numBands = sampleModel.getNumBands(); + if (numBands < 1 || numBands > 4) { + return false; + } + + boolean hasAlpha = colorModel.hasAlpha(); + // Fix 4464413: PNGTransparency reg-test was failing + // because for IndexColorModels that have alpha, + // numBands == 1 && hasAlpha == true, thus causing + // the check below to fail and return false. + if (colorModel instanceof IndexColorModel) { + return true; + } + if ((numBands == 1 || numBands == 3) && hasAlpha) { + return false; + } + if ((numBands == 2 || numBands == 4) && !hasAlpha) { + return false; + } + + return true; + } + + public String getDescription(Locale locale) { + return "Standard PNG image writer"; + } + + public ImageWriter createWriterInstance(Object extension) { + return new PNGImageWriter(this); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadata.java new file mode 100644 index 000000000..725697054 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadata.java @@ -0,0 +1,2047 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.SampleModel; +import java.util.ArrayList; +import java.util.StringTokenizer; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; +import org.w3c.dom.Node; + +public class PNGMetadata extends IIOMetadata implements Cloneable { + + // package scope + public static final String + nativeMetadataFormatName = "javax_imageio_png_1.0"; + + protected static final String nativeMetadataFormatClassName + = "com.sun.imageio.plugins.png.PNGMetadataFormat"; + + // Color types for IHDR chunk + static final String[] IHDR_colorTypeNames = { + "Grayscale", null, "RGB", "Palette", + "GrayAlpha", null, "RGBAlpha" + }; + + static final int[] IHDR_numChannels = { + 1, 0, 3, 3, 2, 0, 4 + }; + + // Bit depths for IHDR chunk + static final String[] IHDR_bitDepths = { + "1", "2", "4", "8", "16" + }; + + // Compression methods for IHDR chunk + static final String[] IHDR_compressionMethodNames = { + "deflate" + }; + + // Filter methods for IHDR chunk + static final String[] IHDR_filterMethodNames = { + "adaptive" + }; + + // Interlace methods for IHDR chunk + static final String[] IHDR_interlaceMethodNames = { + "none", "adam7" + }; + + // Compression methods for iCCP chunk + static final String[] iCCP_compressionMethodNames = { + "deflate" + }; + + // Compression methods for zTXt chunk + static final String[] zTXt_compressionMethodNames = { + "deflate" + }; + + // "Unknown" unit for pHYs chunk + public static final int PHYS_UNIT_UNKNOWN = 0; + + // "Meter" unit for pHYs chunk + public static final int PHYS_UNIT_METER = 1; + + // Unit specifiers for pHYs chunk + static final String[] unitSpecifierNames = { + "unknown", "meter" + }; + + // Rendering intents for sRGB chunk + static final String[] renderingIntentNames = { + "Perceptual", // 0 + "Relative colorimetric", // 1 + "Saturation", // 2 + "Absolute colorimetric" // 3 + + }; + + // Color space types for Chroma->ColorSpaceType node + static final String[] colorSpaceTypeNames = { + "GRAY", null, "RGB", "RGB", + "GRAY", null, "RGB" + }; + + // IHDR chunk + public boolean IHDR_present; + public int IHDR_width; + public int IHDR_height; + public int IHDR_bitDepth; + public int IHDR_colorType; + public int IHDR_compressionMethod; + public int IHDR_filterMethod; + public int IHDR_interlaceMethod; // 0 == none, 1 == adam7 + + // PLTE chunk + public boolean PLTE_present; + public byte[] PLTE_red; + public byte[] PLTE_green; + public byte[] PLTE_blue; + + // If non-null, used to reorder palette entries during encoding in + // order to minimize the size of the tRNS chunk. Thus an index of + // 'i' in the source should be encoded as index 'PLTE_order[i]'. + // PLTE_order will be null unless 'initialize' is called with an + // IndexColorModel image type. + public int[] PLTE_order = null; + + // bKGD chunk + // If external (non-PNG sourced) data has red = green = blue, + // always store it as gray and promote when writing + public boolean bKGD_present; + public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE + public int bKGD_index; + public int bKGD_gray; + public int bKGD_red; + public int bKGD_green; + public int bKGD_blue; + + // cHRM chunk + public boolean cHRM_present; + public int cHRM_whitePointX; + public int cHRM_whitePointY; + public int cHRM_redX; + public int cHRM_redY; + public int cHRM_greenX; + public int cHRM_greenY; + public int cHRM_blueX; + public int cHRM_blueY; + + // gAMA chunk + public boolean gAMA_present; + public int gAMA_gamma; + + // hIST chunk + public boolean hIST_present; + public char[] hIST_histogram; + + // iCCP chunk + public boolean iCCP_present; + public String iCCP_profileName; + public int iCCP_compressionMethod; + public byte[] iCCP_compressedProfile; + + // iTXt chunk + public ArrayList iTXt_keyword = new ArrayList(); + public ArrayList iTXt_compressionFlag = new ArrayList(); + public ArrayList iTXt_compressionMethod = new ArrayList(); + public ArrayList iTXt_languageTag = new ArrayList(); + public ArrayList iTXt_translatedKeyword = new ArrayList(); + public ArrayList iTXt_text = new ArrayList(); + + // pHYs chunk + public boolean pHYs_present; + public int pHYs_pixelsPerUnitXAxis; + public int pHYs_pixelsPerUnitYAxis; + public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter + + // sBIT chunk + public boolean sBIT_present; + public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA + public int sBIT_grayBits; + public int sBIT_redBits; + public int sBIT_greenBits; + public int sBIT_blueBits; + public int sBIT_alphaBits; + + // sPLT chunk + public boolean sPLT_present; + public String sPLT_paletteName; // 1-79 characters + public int sPLT_sampleDepth; // 8 or 16 + public int[] sPLT_red; + public int[] sPLT_green; + public int[] sPLT_blue; + public int[] sPLT_alpha; + public int[] sPLT_frequency; + + // sRGB chunk + public boolean sRGB_present; + public int sRGB_renderingIntent; + + // tEXt chunk + public ArrayList tEXt_keyword = new ArrayList(); // 1-79 characters + public ArrayList tEXt_text = new ArrayList(); + + // tIME chunk + public boolean tIME_present; + public int tIME_year; + public int tIME_month; + public int tIME_day; + public int tIME_hour; + public int tIME_minute; + public int tIME_second; + + // tRNS chunk + // If external (non-PNG sourced) data has red = green = blue, + // always store it as gray and promote when writing + public boolean tRNS_present; + public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE + public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc. + public int tRNS_gray; + public int tRNS_red; + public int tRNS_green; + public int tRNS_blue; + + // zTXt chunk + public ArrayList zTXt_keyword = new ArrayList(); + public ArrayList zTXt_compressionMethod = new ArrayList(); + public ArrayList zTXt_text = new ArrayList(); + + // Unknown chunks + public ArrayList unknownChunkType = new ArrayList(); + public ArrayList unknownChunkData = new ArrayList(); + + public PNGMetadata() { + super(true, + nativeMetadataFormatName, + nativeMetadataFormatClassName, + null, null); + } + + public PNGMetadata(IIOMetadata metadata) { + // TODO -- implement + } + + /** + * Sets the IHDR_bitDepth and IHDR_colorType variables. + * The numBands parameter is necessary since + * we may only be writing a subset of the image bands. + */ + public void initialize(ImageTypeSpecifier imageType, int numBands) { + ColorModel colorModel = imageType.getColorModel(); + SampleModel sampleModel = imageType.getSampleModel(); + + // Initialize IHDR_bitDepth + int[] sampleSize = sampleModel.getSampleSize(); + int bitDepth = sampleSize[0]; + // Choose max bit depth over all channels + // Fixes bug 4413109 + for (int i = 1; i < sampleSize.length; i++) { + if (sampleSize[i] > bitDepth) { + bitDepth = sampleSize[i]; + } + } + // Multi-channel images must have a bit depth of 8 or 16 + if (sampleSize.length > 1 && bitDepth < 8) { + bitDepth = 8; + } + + // Round bit depth up to a power of 2 + if (bitDepth > 2 && bitDepth < 4) { + bitDepth = 4; + } else if (bitDepth > 4 && bitDepth < 8) { + bitDepth = 8; + } else if (bitDepth > 8 && bitDepth < 16) { + bitDepth = 16; + } else if (bitDepth > 16) { + throw new RuntimeException("bitDepth > 16!"); + } + IHDR_bitDepth = bitDepth; + + // Initialize IHDR_colorType + if (colorModel instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)colorModel; + int size = icm.getMapSize(); + + byte[] reds = new byte[size]; + icm.getReds(reds); + byte[] greens = new byte[size]; + icm.getGreens(greens); + byte[] blues = new byte[size]; + icm.getBlues(blues); + + // Determine whether the color tables are actually a gray ramp + // if the color type has not been set previously + boolean isGray = false; + if (!IHDR_present || + (IHDR_colorType != PNGImageReader.PNG_COLOR_PALETTE)) { + isGray = true; + int scale = 255/((1 << IHDR_bitDepth) - 1); + for (int i = 0; i < size; i++) { + byte red = reds[i]; + if ((red != (byte)(i*scale)) || + (red != greens[i]) || + (red != blues[i])) { + isGray = false; + break; + } + } + } + + // Determine whether transparency exists + boolean hasAlpha = colorModel.hasAlpha(); + + byte[] alpha = null; + if (hasAlpha) { + alpha = new byte[size]; + icm.getAlphas(alpha); + } + + /* + * NB: PNG_COLOR_GRAY_ALPHA color type may be not optimal for images + * contained more than 1024 pixels (or even than 768 pixels in case of + * single transparent pixel in palette). + * For such images alpha samples in raster will occupy more space than + * it is required to store palette so it could be reasonable to + * use PNG_COLOR_PALETTE color type for large images. + */ + + if (isGray && hasAlpha && (bitDepth == 8 || bitDepth == 16)) { + IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; + } else if (isGray && !hasAlpha) { + IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else { + IHDR_colorType = PNGImageReader.PNG_COLOR_PALETTE; + PLTE_present = true; + PLTE_order = null; + PLTE_red = (byte[])reds.clone(); + PLTE_green = (byte[])greens.clone(); + PLTE_blue = (byte[])blues.clone(); + + if (hasAlpha) { + tRNS_present = true; + tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE; + + PLTE_order = new int[alpha.length]; + + // Reorder the palette so that non-opaque entries + // come first. Since the tRNS chunk does not have + // to store trailing 255's, this can save a + // considerable amount of space when encoding + // images with only one transparent pixel value, + // e.g., images from GIF sources. + + byte[] newAlpha = new byte[alpha.length]; + + // Scan for non-opaque entries and assign them + // positions starting at 0. + int newIndex = 0; + for (int i = 0; i < alpha.length; i++) { + if (alpha[i] != (byte)255) { + PLTE_order[i] = newIndex; + newAlpha[newIndex] = alpha[i]; + ++newIndex; + } + } + int numTransparent = newIndex; + + // Scan for opaque entries and assign them + // positions following the non-opaque entries. + for (int i = 0; i < alpha.length; i++) { + if (alpha[i] == (byte)255) { + PLTE_order[i] = newIndex++; + } + } + + // Reorder the palettes + byte[] oldRed = PLTE_red; + byte[] oldGreen = PLTE_green; + byte[] oldBlue = PLTE_blue; + int len = oldRed.length; // All have the same length + PLTE_red = new byte[len]; + PLTE_green = new byte[len]; + PLTE_blue = new byte[len]; + for (int i = 0; i < len; i++) { + PLTE_red[PLTE_order[i]] = oldRed[i]; + PLTE_green[PLTE_order[i]] = oldGreen[i]; + PLTE_blue[PLTE_order[i]] = oldBlue[i]; + } + + // Copy only the transparent entries into tRNS_alpha + tRNS_alpha = new byte[numTransparent]; + System.arraycopy(newAlpha, 0, + tRNS_alpha, 0, numTransparent); + } + } + } else { + if (numBands == 1) { + IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else if (numBands == 2) { + IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; + } else if (numBands == 3) { + IHDR_colorType = PNGImageReader.PNG_COLOR_RGB; + } else if (numBands == 4) { + IHDR_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA; + } else { + throw new RuntimeException("Number of bands not 1-4!"); + } + } + + IHDR_present = true; + } + + public boolean isReadOnly() { + return false; + } + + private ArrayList cloneBytesArrayList(ArrayList in) { + if (in == null) { + return null; + } else { + ArrayList list = new ArrayList(in.size()); + for (byte[] b: in) { + list.add((b == null) ? null : (byte[])b.clone()); + } + return list; + } + } + + // Deep clone + public Object clone() { + PNGMetadata metadata; + try { + metadata = (PNGMetadata)super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + + // unknownChunkData needs deep clone + metadata.unknownChunkData = + cloneBytesArrayList(this.unknownChunkData); + + return metadata; + } + + public Node getAsTree(String formatName) { + if (formatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + private Node getNativeTree() { + IIOMetadataNode node = null; // scratch node + IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); + + // IHDR + if (IHDR_present) { + IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR"); + IHDR_node.setAttribute("width", Integer.toString(IHDR_width)); + IHDR_node.setAttribute("height", Integer.toString(IHDR_height)); + IHDR_node.setAttribute("bitDepth", + Integer.toString(IHDR_bitDepth)); + IHDR_node.setAttribute("colorType", + IHDR_colorTypeNames[IHDR_colorType]); + // IHDR_compressionMethod must be 0 in PNG 1.1 + IHDR_node.setAttribute("compressionMethod", + IHDR_compressionMethodNames[IHDR_compressionMethod]); + // IHDR_filterMethod must be 0 in PNG 1.1 + IHDR_node.setAttribute("filterMethod", + IHDR_filterMethodNames[IHDR_filterMethod]); + IHDR_node.setAttribute("interlaceMethod", + IHDR_interlaceMethodNames[IHDR_interlaceMethod]); + root.appendChild(IHDR_node); + } + + // PLTE + if (PLTE_present) { + IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE"); + int numEntries = PLTE_red.length; + for (int i = 0; i < numEntries; i++) { + IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", + Integer.toString(PLTE_red[i] & 0xff)); + entry.setAttribute("green", + Integer.toString(PLTE_green[i] & 0xff)); + entry.setAttribute("blue", + Integer.toString(PLTE_blue[i] & 0xff)); + PLTE_node.appendChild(entry); + } + + root.appendChild(PLTE_node); + } + + // bKGD + if (bKGD_present) { + IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD"); + + if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { + node = new IIOMetadataNode("bKGD_Palette"); + node.setAttribute("index", Integer.toString(bKGD_index)); + } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) { + node = new IIOMetadataNode("bKGD_Grayscale"); + node.setAttribute("gray", Integer.toString(bKGD_gray)); + } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_RGB) { + node = new IIOMetadataNode("bKGD_RGB"); + node.setAttribute("red", Integer.toString(bKGD_red)); + node.setAttribute("green", Integer.toString(bKGD_green)); + node.setAttribute("blue", Integer.toString(bKGD_blue)); + } + bKGD_node.appendChild(node); + + root.appendChild(bKGD_node); + } + + // cHRM + if (cHRM_present) { + IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM"); + cHRM_node.setAttribute("whitePointX", + Integer.toString(cHRM_whitePointX)); + cHRM_node.setAttribute("whitePointY", + Integer.toString(cHRM_whitePointY)); + cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX)); + cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY)); + cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX)); + cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY)); + cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX)); + cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY)); + + root.appendChild(cHRM_node); + } + + // gAMA + if (gAMA_present) { + IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA"); + gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma)); + + root.appendChild(gAMA_node); + } + + // hIST + if (hIST_present) { + IIOMetadataNode hIST_node = new IIOMetadataNode("hIST"); + + for (int i = 0; i < hIST_histogram.length; i++) { + IIOMetadataNode hist = + new IIOMetadataNode("hISTEntry"); + hist.setAttribute("index", Integer.toString(i)); + hist.setAttribute("value", + Integer.toString(hIST_histogram[i])); + hIST_node.appendChild(hist); + } + + root.appendChild(hIST_node); + } + + // iCCP + if (iCCP_present) { + IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP"); + iCCP_node.setAttribute("profileName", iCCP_profileName); + iCCP_node.setAttribute("compressionMethod", + iCCP_compressionMethodNames[iCCP_compressionMethod]); + + Object profile = iCCP_compressedProfile; + if (profile != null) { + profile = ((byte[])profile).clone(); + } + iCCP_node.setUserObject(profile); + + root.appendChild(iCCP_node); + } + + // iTXt + if (iTXt_keyword.size() > 0) { + IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt"); + for (int i = 0; i < iTXt_keyword.size(); i++) { + IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry"); + iTXt_node.setAttribute("keyword", iTXt_keyword.get(i)); + iTXt_node.setAttribute("compressionFlag", + iTXt_compressionFlag.get(i) ? "TRUE" : "FALSE"); + iTXt_node.setAttribute("compressionMethod", + iTXt_compressionMethod.get(i).toString()); + iTXt_node.setAttribute("languageTag", + iTXt_languageTag.get(i)); + iTXt_node.setAttribute("translatedKeyword", + iTXt_translatedKeyword.get(i)); + iTXt_node.setAttribute("text", iTXt_text.get(i)); + + iTXt_parent.appendChild(iTXt_node); + } + + root.appendChild(iTXt_parent); + } + + // pHYs + if (pHYs_present) { + IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs"); + pHYs_node.setAttribute("pixelsPerUnitXAxis", + Integer.toString(pHYs_pixelsPerUnitXAxis)); + pHYs_node.setAttribute("pixelsPerUnitYAxis", + Integer.toString(pHYs_pixelsPerUnitYAxis)); + pHYs_node.setAttribute("unitSpecifier", + unitSpecifierNames[pHYs_unitSpecifier]); + + root.appendChild(pHYs_node); + } + + // sBIT + if (sBIT_present) { + IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT"); + + if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY) { + node = new IIOMetadataNode("sBIT_Grayscale"); + node.setAttribute("gray", + Integer.toString(sBIT_grayBits)); + } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + node = new IIOMetadataNode("sBIT_GrayAlpha"); + node.setAttribute("gray", + Integer.toString(sBIT_grayBits)); + node.setAttribute("alpha", + Integer.toString(sBIT_alphaBits)); + } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB) { + node = new IIOMetadataNode("sBIT_RGB"); + node.setAttribute("red", + Integer.toString(sBIT_redBits)); + node.setAttribute("green", + Integer.toString(sBIT_greenBits)); + node.setAttribute("blue", + Integer.toString(sBIT_blueBits)); + } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { + node = new IIOMetadataNode("sBIT_RGBAlpha"); + node.setAttribute("red", + Integer.toString(sBIT_redBits)); + node.setAttribute("green", + Integer.toString(sBIT_greenBits)); + node.setAttribute("blue", + Integer.toString(sBIT_blueBits)); + node.setAttribute("alpha", + Integer.toString(sBIT_alphaBits)); + } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_PALETTE) { + node = new IIOMetadataNode("sBIT_Palette"); + node.setAttribute("red", + Integer.toString(sBIT_redBits)); + node.setAttribute("green", + Integer.toString(sBIT_greenBits)); + node.setAttribute("blue", + Integer.toString(sBIT_blueBits)); + } + sBIT_node.appendChild(node); + + root.appendChild(sBIT_node); + } + + // sPLT + if (sPLT_present) { + IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT"); + + sPLT_node.setAttribute("name", sPLT_paletteName); + sPLT_node.setAttribute("sampleDepth", + Integer.toString(sPLT_sampleDepth)); + + int numEntries = sPLT_red.length; + for (int i = 0; i < numEntries; i++) { + IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", Integer.toString(sPLT_red[i])); + entry.setAttribute("green", Integer.toString(sPLT_green[i])); + entry.setAttribute("blue", Integer.toString(sPLT_blue[i])); + entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i])); + entry.setAttribute("frequency", + Integer.toString(sPLT_frequency[i])); + sPLT_node.appendChild(entry); + } + + root.appendChild(sPLT_node); + } + + // sRGB + if (sRGB_present) { + IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB"); + sRGB_node.setAttribute("renderingIntent", + renderingIntentNames[sRGB_renderingIntent]); + + root.appendChild(sRGB_node); + } + + // tEXt + if (tEXt_keyword.size() > 0) { + IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt"); + for (int i = 0; i < tEXt_keyword.size(); i++) { + IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry"); + tEXt_node.setAttribute("keyword" , (String)tEXt_keyword.get(i)); + tEXt_node.setAttribute("value" , (String)tEXt_text.get(i)); + + tEXt_parent.appendChild(tEXt_node); + } + + root.appendChild(tEXt_parent); + } + + // tIME + if (tIME_present) { + IIOMetadataNode tIME_node = new IIOMetadataNode("tIME"); + tIME_node.setAttribute("year", Integer.toString(tIME_year)); + tIME_node.setAttribute("month", Integer.toString(tIME_month)); + tIME_node.setAttribute("day", Integer.toString(tIME_day)); + tIME_node.setAttribute("hour", Integer.toString(tIME_hour)); + tIME_node.setAttribute("minute", Integer.toString(tIME_minute)); + tIME_node.setAttribute("second", Integer.toString(tIME_second)); + + root.appendChild(tIME_node); + } + + // tRNS + if (tRNS_present) { + IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS"); + + if (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE) { + node = new IIOMetadataNode("tRNS_Palette"); + + for (int i = 0; i < tRNS_alpha.length; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("tRNS_PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("alpha", + Integer.toString(tRNS_alpha[i] & 0xff)); + node.appendChild(entry); + } + } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) { + node = new IIOMetadataNode("tRNS_Grayscale"); + node.setAttribute("gray", Integer.toString(tRNS_gray)); + } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) { + node = new IIOMetadataNode("tRNS_RGB"); + node.setAttribute("red", Integer.toString(tRNS_red)); + node.setAttribute("green", Integer.toString(tRNS_green)); + node.setAttribute("blue", Integer.toString(tRNS_blue)); + } + tRNS_node.appendChild(node); + + root.appendChild(tRNS_node); + } + + // zTXt + if (zTXt_keyword.size() > 0) { + IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt"); + for (int i = 0; i < zTXt_keyword.size(); i++) { + IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry"); + zTXt_node.setAttribute("keyword", (String)zTXt_keyword.get(i)); + + int cm = ((Integer)zTXt_compressionMethod.get(i)).intValue(); + zTXt_node.setAttribute("compressionMethod", + zTXt_compressionMethodNames[cm]); + + zTXt_node.setAttribute("text", (String)zTXt_text.get(i)); + + zTXt_parent.appendChild(zTXt_node); + } + + root.appendChild(zTXt_parent); + } + + // Unknown chunks + if (unknownChunkType.size() > 0) { + IIOMetadataNode unknown_parent = + new IIOMetadataNode("UnknownChunks"); + for (int i = 0; i < unknownChunkType.size(); i++) { + IIOMetadataNode unknown_node = + new IIOMetadataNode("UnknownChunk"); + unknown_node.setAttribute("type", + (String)unknownChunkType.get(i)); + unknown_node.setUserObject((byte[])unknownChunkData.get(i)); + + unknown_parent.appendChild(unknown_node); + } + + root.appendChild(unknown_parent); + } + + return root; + } + + private int getNumChannels() { + // Determine number of channels + // Be careful about palette color with transparency + int numChannels = IHDR_numChannels[IHDR_colorType]; + if (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE && + tRNS_present && tRNS_colorType == IHDR_colorType) { + numChannels = 4; + } + return numChannels; + } + + public IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("ColorSpaceType"); + node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]); + chroma_node.appendChild(node); + + node = new IIOMetadataNode("NumChannels"); + node.setAttribute("value", Integer.toString(getNumChannels())); + chroma_node.appendChild(node); + + if (gAMA_present) { + node = new IIOMetadataNode("Gamma"); + node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F)); + chroma_node.appendChild(node); + } + + node = new IIOMetadataNode("BlackIsZero"); + node.setAttribute("value", "TRUE"); + chroma_node.appendChild(node); + + if (PLTE_present) { + boolean hasAlpha = tRNS_present && + (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE); + + node = new IIOMetadataNode("Palette"); + for (int i = 0; i < PLTE_red.length; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", + Integer.toString(PLTE_red[i] & 0xff)); + entry.setAttribute("green", + Integer.toString(PLTE_green[i] & 0xff)); + entry.setAttribute("blue", + Integer.toString(PLTE_blue[i] & 0xff)); + if (hasAlpha) { + int alpha = (i < tRNS_alpha.length) ? + (tRNS_alpha[i] & 0xff) : 255; + entry.setAttribute("alpha", Integer.toString(alpha)); + } + node.appendChild(entry); + } + chroma_node.appendChild(node); + } + + if (bKGD_present) { + if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { + node = new IIOMetadataNode("BackgroundIndex"); + node.setAttribute("value", Integer.toString(bKGD_index)); + } else { + node = new IIOMetadataNode("BackgroundColor"); + int r, g, b; + + if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) { + r = g = b = bKGD_gray; + } else { + r = bKGD_red; + g = bKGD_green; + b = bKGD_blue; + } + node.setAttribute("red", Integer.toString(r)); + node.setAttribute("green", Integer.toString(g)); + node.setAttribute("blue", Integer.toString(b)); + } + chroma_node.appendChild(node); + } + + return chroma_node; + } + + public IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("CompressionTypeName"); + node.setAttribute("value", "deflate"); + compression_node.appendChild(node); + + node = new IIOMetadataNode("Lossless"); + node.setAttribute("value", "TRUE"); + compression_node.appendChild(node); + + node = new IIOMetadataNode("NumProgressiveScans"); + node.setAttribute("value", + (IHDR_interlaceMethod == 0) ? "1" : "7"); + compression_node.appendChild(node); + + return compression_node; + } + + private String repeat(String s, int times) { + if (times == 1) { + return s; + } + StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1); + sb.append(s); + for (int i = 1; i < times; i++) { + sb.append(" "); + sb.append(s); + } + return sb.toString(); + } + + public IIOMetadataNode getStandardDataNode() { + IIOMetadataNode data_node = new IIOMetadataNode("Data"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("PlanarConfiguration"); + node.setAttribute("value", "PixelInterleaved"); + data_node.appendChild(node); + + node = new IIOMetadataNode("SampleFormat"); + node.setAttribute("value", + IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE ? + "Index" : "UnsignedIntegral"); + data_node.appendChild(node); + + String bitDepth = Integer.toString(IHDR_bitDepth); + node = new IIOMetadataNode("BitsPerSample"); + node.setAttribute("value", repeat(bitDepth, getNumChannels())); + data_node.appendChild(node); + + if (sBIT_present) { + node = new IIOMetadataNode("SignificantBitsPerSample"); + String sbits; + if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY || + sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) { + sbits = Integer.toString(sBIT_grayBits); + } else { // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB || + // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA + sbits = Integer.toString(sBIT_redBits) + " " + + Integer.toString(sBIT_greenBits) + " " + + Integer.toString(sBIT_blueBits); + } + + if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA || + sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) { + sbits += " " + Integer.toString(sBIT_alphaBits); + } + + node.setAttribute("value", sbits); + data_node.appendChild(node); + } + + // SampleMSB + + return data_node; + } + + public IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("PixelAspectRatio"); + float ratio = pHYs_present ? + (float)pHYs_pixelsPerUnitXAxis/pHYs_pixelsPerUnitYAxis : 1.0F; + node.setAttribute("value", Float.toString(ratio)); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("ImageOrientation"); + node.setAttribute("value", "Normal"); + dimension_node.appendChild(node); + + if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) { + node = new IIOMetadataNode("HorizontalPixelSize"); + node.setAttribute("value", + Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis)); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("VerticalPixelSize"); + node.setAttribute("value", + Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis)); + dimension_node.appendChild(node); + } + + return dimension_node; + } + + public IIOMetadataNode getStandardDocumentNode() { + if (!tIME_present) { + return null; + } + + IIOMetadataNode document_node = new IIOMetadataNode("Document"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("ImageModificationTime"); + node.setAttribute("year", Integer.toString(tIME_year)); + node.setAttribute("month", Integer.toString(tIME_month)); + node.setAttribute("day", Integer.toString(tIME_day)); + node.setAttribute("hour", Integer.toString(tIME_hour)); + node.setAttribute("minute", Integer.toString(tIME_minute)); + node.setAttribute("second", Integer.toString(tIME_second)); + document_node.appendChild(node); + + return document_node; + } + + public IIOMetadataNode getStandardTextNode() { + int numEntries = tEXt_keyword.size() + + iTXt_keyword.size() + zTXt_keyword.size(); + if (numEntries == 0) { + return null; + } + + IIOMetadataNode text_node = new IIOMetadataNode("Text"); + IIOMetadataNode node = null; // scratch node + + for (int i = 0; i < tEXt_keyword.size(); i++) { + node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", (String)tEXt_keyword.get(i)); + node.setAttribute("value", (String)tEXt_text.get(i)); + node.setAttribute("encoding", "ISO-8859-1"); + node.setAttribute("compression", "none"); + + text_node.appendChild(node); + } + + for (int i = 0; i < iTXt_keyword.size(); i++) { + node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", iTXt_keyword.get(i)); + node.setAttribute("value", iTXt_text.get(i)); + node.setAttribute("language", + iTXt_languageTag.get(i)); + if (iTXt_compressionFlag.get(i)) { + node.setAttribute("compression", "zip"); + } else { + node.setAttribute("compression", "none"); + } + + text_node.appendChild(node); + } + + for (int i = 0; i < zTXt_keyword.size(); i++) { + node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", (String)zTXt_keyword.get(i)); + node.setAttribute("value", (String)zTXt_text.get(i)); + node.setAttribute("compression", "zip"); + + text_node.appendChild(node); + } + + return text_node; + } + + public IIOMetadataNode getStandardTransparencyNode() { + IIOMetadataNode transparency_node = + new IIOMetadataNode("Transparency"); + IIOMetadataNode node = null; // scratch node + + node = new IIOMetadataNode("Alpha"); + boolean hasAlpha = + (IHDR_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) || + (IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) || + (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE && + tRNS_present && + (tRNS_colorType == IHDR_colorType) && + (tRNS_alpha != null)); + node.setAttribute("value", hasAlpha ? "nonpremultipled" : "none"); + transparency_node.appendChild(node); + + if (tRNS_present) { + node = new IIOMetadataNode("TransparentColor"); + if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) { + node.setAttribute("value", + Integer.toString(tRNS_red) + " " + + Integer.toString(tRNS_green) + " " + + Integer.toString(tRNS_blue)); + } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) { + node.setAttribute("value", Integer.toString(tRNS_gray)); + } + transparency_node.appendChild(node); + } + + return transparency_node; + } + + // Shorthand for throwing an IIOInvalidTreeException + private void fatal(Node node, String reason) + throws IIOInvalidTreeException { + throw new IIOInvalidTreeException(reason, node); + } + + // Get an integer-valued attribute + private String getStringAttribute(Node node, String name, + String defaultValue, boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + return attr.getNodeValue(); + } + + + // Get an integer-valued attribute + private int getIntAttribute(Node node, String name, + int defaultValue, boolean required) + throws IIOInvalidTreeException { + String value = getStringAttribute(node, name, null, required); + if (value == null) { + return defaultValue; + } + return Integer.parseInt(value); + } + + // Get a float-valued attribute + private float getFloatAttribute(Node node, String name, + float defaultValue, boolean required) + throws IIOInvalidTreeException { + String value = getStringAttribute(node, name, null, required); + if (value == null) { + return defaultValue; + } + return Float.parseFloat(value); + } + + // Get a required integer-valued attribute + private int getIntAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getIntAttribute(node, name, -1, true); + } + + // Get a required float-valued attribute + private float getFloatAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getFloatAttribute(node, name, -1.0F, true); + } + + // Get a boolean-valued attribute + private boolean getBooleanAttribute(Node node, String name, + boolean defaultValue, + boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + String value = attr.getNodeValue(); + // Allow lower case booleans for backward compatibility, #5082756 + if (value.equals("TRUE") || value.equals("true")) { + return true; + } else if (value.equals("FALSE") || value.equals("false")) { + return false; + } else { + fatal(node, "Attribute " + name + " must be 'TRUE' or 'FALSE'!"); + return false; + } + } + + // Get a required boolean-valued attribute + private boolean getBooleanAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getBooleanAttribute(node, name, false, true); + } + + // Get an enumerated attribute as an index into a String array + private int getEnumeratedAttribute(Node node, + String name, String[] legalNames, + int defaultValue, boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + String value = attr.getNodeValue(); + for (int i = 0; i < legalNames.length; i++) { + if (value.equals(legalNames[i])) { + return i; + } + } + + fatal(node, "Illegal value for attribute " + name + "!"); + return -1; + } + + // Get a required enumerated attribute as an index into a String array + private int getEnumeratedAttribute(Node node, + String name, String[] legalNames) + throws IIOInvalidTreeException { + return getEnumeratedAttribute(node, name, legalNames, -1, true); + } + + // Get a String-valued attribute + private String getAttribute(Node node, String name, + String defaultValue, boolean required) + throws IIOInvalidTreeException { + Node attr = node.getAttributes().getNamedItem(name); + if (attr == null) { + if (!required) { + return defaultValue; + } else { + fatal(node, "Required attribute " + name + " not present!"); + } + } + return attr.getNodeValue(); + } + + // Get a required String-valued attribute + private String getAttribute(Node node, String name) + throws IIOInvalidTreeException { + return getAttribute(node, name, null, true); + } + + public void mergeTree(String formatName, Node root) + throws IIOInvalidTreeException { + if (formatName.equals(nativeMetadataFormatName)) { + if (root == null) { + throw new IllegalArgumentException("root == null!"); + } + mergeNativeTree(root); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + if (root == null) { + throw new IllegalArgumentException("root == null!"); + } + mergeStandardTree(root); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + private void mergeNativeTree(Node root) + throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName().equals(nativeMetadataFormatName)) { + fatal(node, "Root must be " + nativeMetadataFormatName); + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("IHDR")) { + IHDR_width = getIntAttribute(node, "width"); + IHDR_height = getIntAttribute(node, "height"); + IHDR_bitDepth = + Integer.valueOf(IHDR_bitDepths[ + getEnumeratedAttribute(node, + "bitDepth", + IHDR_bitDepths)]); + IHDR_colorType = getEnumeratedAttribute(node, "colorType", + IHDR_colorTypeNames); + IHDR_compressionMethod = + getEnumeratedAttribute(node, "compressionMethod", + IHDR_compressionMethodNames); + IHDR_filterMethod = + getEnumeratedAttribute(node, + "filterMethod", + IHDR_filterMethodNames); + IHDR_interlaceMethod = + getEnumeratedAttribute(node, "interlaceMethod", + IHDR_interlaceMethodNames); + IHDR_present = true; + } else if (name.equals("PLTE")) { + byte[] red = new byte[256]; + byte[] green = new byte[256]; + byte[] blue = new byte[256]; + int maxindex = -1; + + Node PLTE_entry = node.getFirstChild(); + if (PLTE_entry == null) { + fatal(node, "Palette has no entries!"); + } + + while (PLTE_entry != null) { + if (!PLTE_entry.getNodeName().equals("PLTEEntry")) { + fatal(node, + "Only a PLTEEntry may be a child of a PLTE!"); + } + + int index = getIntAttribute(PLTE_entry, "index"); + if (index < 0 || index > 255) { + fatal(node, + "Bad value for PLTEEntry attribute index!"); + } + if (index > maxindex) { + maxindex = index; + } + red[index] = + (byte)getIntAttribute(PLTE_entry, "red"); + green[index] = + (byte)getIntAttribute(PLTE_entry, "green"); + blue[index] = + (byte)getIntAttribute(PLTE_entry, "blue"); + + PLTE_entry = PLTE_entry.getNextSibling(); + } + + int numEntries = maxindex + 1; + PLTE_red = new byte[numEntries]; + PLTE_green = new byte[numEntries]; + PLTE_blue = new byte[numEntries]; + System.arraycopy(red, 0, PLTE_red, 0, numEntries); + System.arraycopy(green, 0, PLTE_green, 0, numEntries); + System.arraycopy(blue, 0, PLTE_blue, 0, numEntries); + PLTE_present = true; + } else if (name.equals("bKGD")) { + bKGD_present = false; // Guard against partial overwrite + Node bKGD_node = node.getFirstChild(); + if (bKGD_node == null) { + fatal(node, "bKGD node has no children!"); + } + String bKGD_name = bKGD_node.getNodeName(); + if (bKGD_name.equals("bKGD_Palette")) { + bKGD_index = getIntAttribute(bKGD_node, "index"); + bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE; + } else if (bKGD_name.equals("bKGD_Grayscale")) { + bKGD_gray = getIntAttribute(bKGD_node, "gray"); + bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else if (bKGD_name.equals("bKGD_RGB")) { + bKGD_red = getIntAttribute(bKGD_node, "red"); + bKGD_green = getIntAttribute(bKGD_node, "green"); + bKGD_blue = getIntAttribute(bKGD_node, "blue"); + bKGD_colorType = PNGImageReader.PNG_COLOR_RGB; + } else { + fatal(node, "Bad child of a bKGD node!"); + } + if (bKGD_node.getNextSibling() != null) { + fatal(node, "bKGD node has more than one child!"); + } + + bKGD_present = true; + } else if (name.equals("cHRM")) { + cHRM_whitePointX = getIntAttribute(node, "whitePointX"); + cHRM_whitePointY = getIntAttribute(node, "whitePointY"); + cHRM_redX = getIntAttribute(node, "redX"); + cHRM_redY = getIntAttribute(node, "redY"); + cHRM_greenX = getIntAttribute(node, "greenX"); + cHRM_greenY = getIntAttribute(node, "greenY"); + cHRM_blueX = getIntAttribute(node, "blueX"); + cHRM_blueY = getIntAttribute(node, "blueY"); + + cHRM_present = true; + } else if (name.equals("gAMA")) { + gAMA_gamma = getIntAttribute(node, "value"); + gAMA_present = true; + } else if (name.equals("hIST")) { + char[] hist = new char[256]; + int maxindex = -1; + + Node hIST_entry = node.getFirstChild(); + if (hIST_entry == null) { + fatal(node, "hIST node has no children!"); + } + + while (hIST_entry != null) { + if (!hIST_entry.getNodeName().equals("hISTEntry")) { + fatal(node, + "Only a hISTEntry may be a child of a hIST!"); + } + + int index = getIntAttribute(hIST_entry, "index"); + if (index < 0 || index > 255) { + fatal(node, + "Bad value for histEntry attribute index!"); + } + if (index > maxindex) { + maxindex = index; + } + hist[index] = + (char)getIntAttribute(hIST_entry, "value"); + + hIST_entry = hIST_entry.getNextSibling(); + } + + int numEntries = maxindex + 1; + hIST_histogram = new char[numEntries]; + System.arraycopy(hist, 0, hIST_histogram, 0, numEntries); + + hIST_present = true; + } else if (name.equals("iCCP")) { + iCCP_profileName = getAttribute(node, "profileName"); + iCCP_compressionMethod = + getEnumeratedAttribute(node, "compressionMethod", + iCCP_compressionMethodNames); + Object compressedProfile = + ((IIOMetadataNode)node).getUserObject(); + if (compressedProfile == null) { + fatal(node, "No ICCP profile present in user object!"); + } + if (!(compressedProfile instanceof byte[])) { + fatal(node, "User object not a byte array!"); + } + + iCCP_compressedProfile = + (byte[])((byte[])compressedProfile).clone(); + + iCCP_present = true; + } else if (name.equals("iTXt")) { + Node iTXt_node = node.getFirstChild(); + while (iTXt_node != null) { + if (!iTXt_node.getNodeName().equals("iTXtEntry")) { + fatal(node, + "Only an iTXtEntry may be a child of an iTXt!"); + } + + String keyword = getAttribute(iTXt_node, "keyword"); + if (isValidKeyword(keyword)) { + iTXt_keyword.add(keyword); + + boolean compressionFlag = + getBooleanAttribute(iTXt_node, "compressionFlag"); + iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag)); + + String compressionMethod = + getAttribute(iTXt_node, "compressionMethod"); + iTXt_compressionMethod.add(Integer.valueOf(compressionMethod)); + + String languageTag = + getAttribute(iTXt_node, "languageTag"); + iTXt_languageTag.add(languageTag); + + String translatedKeyword = + getAttribute(iTXt_node, "translatedKeyword"); + iTXt_translatedKeyword.add(translatedKeyword); + + String text = getAttribute(iTXt_node, "text"); + iTXt_text.add(text); + + } + // silently skip invalid text entry + + iTXt_node = iTXt_node.getNextSibling(); + } + } else if (name.equals("pHYs")) { + pHYs_pixelsPerUnitXAxis = + getIntAttribute(node, "pixelsPerUnitXAxis"); + pHYs_pixelsPerUnitYAxis = + getIntAttribute(node, "pixelsPerUnitYAxis"); + pHYs_unitSpecifier = + getEnumeratedAttribute(node, "unitSpecifier", + unitSpecifierNames); + + pHYs_present = true; + } else if (name.equals("sBIT")) { + sBIT_present = false; // Guard against partial overwrite + Node sBIT_node = node.getFirstChild(); + if (sBIT_node == null) { + fatal(node, "sBIT node has no children!"); + } + String sBIT_name = sBIT_node.getNodeName(); + if (sBIT_name.equals("sBIT_Grayscale")) { + sBIT_grayBits = getIntAttribute(sBIT_node, "gray"); + sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else if (sBIT_name.equals("sBIT_GrayAlpha")) { + sBIT_grayBits = getIntAttribute(sBIT_node, "gray"); + sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha"); + sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA; + } else if (sBIT_name.equals("sBIT_RGB")) { + sBIT_redBits = getIntAttribute(sBIT_node, "red"); + sBIT_greenBits = getIntAttribute(sBIT_node, "green"); + sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); + sBIT_colorType = PNGImageReader.PNG_COLOR_RGB; + } else if (sBIT_name.equals("sBIT_RGBAlpha")) { + sBIT_redBits = getIntAttribute(sBIT_node, "red"); + sBIT_greenBits = getIntAttribute(sBIT_node, "green"); + sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); + sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha"); + sBIT_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA; + } else if (sBIT_name.equals("sBIT_Palette")) { + sBIT_redBits = getIntAttribute(sBIT_node, "red"); + sBIT_greenBits = getIntAttribute(sBIT_node, "green"); + sBIT_blueBits = getIntAttribute(sBIT_node, "blue"); + sBIT_colorType = PNGImageReader.PNG_COLOR_PALETTE; + } else { + fatal(node, "Bad child of an sBIT node!"); + } + if (sBIT_node.getNextSibling() != null) { + fatal(node, "sBIT node has more than one child!"); + } + + sBIT_present = true; + } else if (name.equals("sPLT")) { + sPLT_paletteName = getAttribute(node, "name"); + sPLT_sampleDepth = getIntAttribute(node, "sampleDepth"); + + int[] red = new int[256]; + int[] green = new int[256]; + int[] blue = new int[256]; + int[] alpha = new int[256]; + int[] frequency = new int[256]; + int maxindex = -1; + + Node sPLT_entry = node.getFirstChild(); + if (sPLT_entry == null) { + fatal(node, "sPLT node has no children!"); + } + + while (sPLT_entry != null) { + if (!sPLT_entry.getNodeName().equals("sPLTEntry")) { + fatal(node, + "Only an sPLTEntry may be a child of an sPLT!"); + } + + int index = getIntAttribute(sPLT_entry, "index"); + if (index < 0 || index > 255) { + fatal(node, + "Bad value for PLTEEntry attribute index!"); + } + if (index > maxindex) { + maxindex = index; + } + red[index] = getIntAttribute(sPLT_entry, "red"); + green[index] = getIntAttribute(sPLT_entry, "green"); + blue[index] = getIntAttribute(sPLT_entry, "blue"); + alpha[index] = getIntAttribute(sPLT_entry, "alpha"); + frequency[index] = + getIntAttribute(sPLT_entry, "frequency"); + + sPLT_entry = sPLT_entry.getNextSibling(); + } + + int numEntries = maxindex + 1; + sPLT_red = new int[numEntries]; + sPLT_green = new int[numEntries]; + sPLT_blue = new int[numEntries]; + sPLT_alpha = new int[numEntries]; + sPLT_frequency = new int[numEntries]; + System.arraycopy(red, 0, sPLT_red, 0, numEntries); + System.arraycopy(green, 0, sPLT_green, 0, numEntries); + System.arraycopy(blue, 0, sPLT_blue, 0, numEntries); + System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries); + System.arraycopy(frequency, 0, + sPLT_frequency, 0, numEntries); + + sPLT_present = true; + } else if (name.equals("sRGB")) { + sRGB_renderingIntent = + getEnumeratedAttribute(node, "renderingIntent", + renderingIntentNames); + + sRGB_present = true; + } else if (name.equals("tEXt")) { + Node tEXt_node = node.getFirstChild(); + while (tEXt_node != null) { + if (!tEXt_node.getNodeName().equals("tEXtEntry")) { + fatal(node, + "Only an tEXtEntry may be a child of an tEXt!"); + } + + String keyword = getAttribute(tEXt_node, "keyword"); + tEXt_keyword.add(keyword); + + String text = getAttribute(tEXt_node, "value"); + tEXt_text.add(text); + + tEXt_node = tEXt_node.getNextSibling(); + } + } else if (name.equals("tIME")) { + tIME_year = getIntAttribute(node, "year"); + tIME_month = getIntAttribute(node, "month"); + tIME_day = getIntAttribute(node, "day"); + tIME_hour = getIntAttribute(node, "hour"); + tIME_minute = getIntAttribute(node, "minute"); + tIME_second = getIntAttribute(node, "second"); + + tIME_present = true; + } else if (name.equals("tRNS")) { + tRNS_present = false; // Guard against partial overwrite + Node tRNS_node = node.getFirstChild(); + if (tRNS_node == null) { + fatal(node, "tRNS node has no children!"); + } + String tRNS_name = tRNS_node.getNodeName(); + if (tRNS_name.equals("tRNS_Palette")) { + byte[] alpha = new byte[256]; + int maxindex = -1; + + Node tRNS_paletteEntry = tRNS_node.getFirstChild(); + if (tRNS_paletteEntry == null) { + fatal(node, "tRNS_Palette node has no children!"); + } + while (tRNS_paletteEntry != null) { + if (!tRNS_paletteEntry.getNodeName().equals( + "tRNS_PaletteEntry")) { + fatal(node, + "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!"); + } + int index = + getIntAttribute(tRNS_paletteEntry, "index"); + if (index < 0 || index > 255) { + fatal(node, + "Bad value for tRNS_PaletteEntry attribute index!"); + } + if (index > maxindex) { + maxindex = index; + } + alpha[index] = + (byte)getIntAttribute(tRNS_paletteEntry, + "alpha"); + + tRNS_paletteEntry = + tRNS_paletteEntry.getNextSibling(); + } + + int numEntries = maxindex + 1; + tRNS_alpha = new byte[numEntries]; + tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE; + System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries); + } else if (tRNS_name.equals("tRNS_Grayscale")) { + tRNS_gray = getIntAttribute(tRNS_node, "gray"); + tRNS_colorType = PNGImageReader.PNG_COLOR_GRAY; + } else if (tRNS_name.equals("tRNS_RGB")) { + tRNS_red = getIntAttribute(tRNS_node, "red"); + tRNS_green = getIntAttribute(tRNS_node, "green"); + tRNS_blue = getIntAttribute(tRNS_node, "blue"); + tRNS_colorType = PNGImageReader.PNG_COLOR_RGB; + } else { + fatal(node, "Bad child of a tRNS node!"); + } + if (tRNS_node.getNextSibling() != null) { + fatal(node, "tRNS node has more than one child!"); + } + + tRNS_present = true; + } else if (name.equals("zTXt")) { + Node zTXt_node = node.getFirstChild(); + while (zTXt_node != null) { + if (!zTXt_node.getNodeName().equals("zTXtEntry")) { + fatal(node, + "Only an zTXtEntry may be a child of an zTXt!"); + } + + String keyword = getAttribute(zTXt_node, "keyword"); + zTXt_keyword.add(keyword); + + int compressionMethod = + getEnumeratedAttribute(zTXt_node, "compressionMethod", + zTXt_compressionMethodNames); + zTXt_compressionMethod.add(new Integer(compressionMethod)); + + String text = getAttribute(zTXt_node, "text"); + zTXt_text.add(text); + + zTXt_node = zTXt_node.getNextSibling(); + } + } else if (name.equals("UnknownChunks")) { + Node unknown_node = node.getFirstChild(); + while (unknown_node != null) { + if (!unknown_node.getNodeName().equals("UnknownChunk")) { + fatal(node, + "Only an UnknownChunk may be a child of an UnknownChunks!"); + } + String chunkType = getAttribute(unknown_node, "type"); + Object chunkData = + ((IIOMetadataNode)unknown_node).getUserObject(); + + if (chunkType.length() != 4) { + fatal(unknown_node, + "Chunk type must be 4 characters!"); + } + if (chunkData == null) { + fatal(unknown_node, + "No chunk data present in user object!"); + } + if (!(chunkData instanceof byte[])) { + fatal(unknown_node, + "User object not a byte array!"); + } + unknownChunkType.add(chunkType); + unknownChunkData.add(((byte[])chunkData).clone()); + + unknown_node = unknown_node.getNextSibling(); + } + } else { + fatal(node, "Unknown child of root node!"); + } + + node = node.getNextSibling(); + } + } + + /* + * Accrding to PNG spec, keywords are restricted to 1 to 79 bytes + * in length. Keywords shall contain only printable Latin-1 characters + * and spaces; To reduce the chances for human misreading of a keyword, + * leading spaces, trailing spaces, and consecutive spaces are not + * permitted in keywords. + * + * See: http://www.w3.org/TR/PNG/#11keywords + */ + private boolean isValidKeyword(String s) { + int len = s.length(); + if (len < 1 || len >= 80) { + return false; + } + if (s.startsWith(" ") || s.endsWith(" ") || s.contains(" ")) { + return false; + } + return isISOLatin(s, false); + } + + /* + * According to PNG spec, keyword shall contain only printable + * Latin-1 [ISO-8859-1] characters and spaces; that is, only + * character codes 32-126 and 161-255 decimal are allowed. + * For Latin-1 value fields the 0x10 (linefeed) control + * character is aloowed too. + * + * See: http://www.w3.org/TR/PNG/#11keywords + */ + private boolean isISOLatin(String s, boolean isLineFeedAllowed) { + int len = s.length(); + for (int i = 0; i < len; i++) { + char c = s.charAt(i); + if (c < 32 || c > 255 || (c > 126 && c < 161)) { + // not printable. Check whether this is an allowed + // control char + if (!isLineFeedAllowed || c != 0x10) { + return false; + } + } + } + return true; + } + + private void mergeStandardTree(Node root) + throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName() + .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + fatal(node, "Root must be " + + IIOMetadataFormatImpl.standardMetadataFormatName); + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("Chroma")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("Gamma")) { + float gamma = getFloatAttribute(child, "value"); + gAMA_present = true; + gAMA_gamma = (int)(gamma*100000 + 0.5); + } else if (childName.equals("Palette")) { + byte[] red = new byte[256]; + byte[] green = new byte[256]; + byte[] blue = new byte[256]; + int maxindex = -1; + + Node entry = child.getFirstChild(); + while (entry != null) { + int index = getIntAttribute(entry, "index"); + if (index >= 0 && index <= 255) { + red[index] = + (byte)getIntAttribute(entry, "red"); + green[index] = + (byte)getIntAttribute(entry, "green"); + blue[index] = + (byte)getIntAttribute(entry, "blue"); + if (index > maxindex) { + maxindex = index; + } + } + entry = entry.getNextSibling(); + } + + int numEntries = maxindex + 1; + PLTE_red = new byte[numEntries]; + PLTE_green = new byte[numEntries]; + PLTE_blue = new byte[numEntries]; + System.arraycopy(red, 0, PLTE_red, 0, numEntries); + System.arraycopy(green, 0, PLTE_green, 0, numEntries); + System.arraycopy(blue, 0, PLTE_blue, 0, numEntries); + PLTE_present = true; + } else if (childName.equals("BackgroundIndex")) { + bKGD_present = true; + bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE; + bKGD_index = getIntAttribute(child, "value"); + } else if (childName.equals("BackgroundColor")) { + int red = getIntAttribute(child, "red"); + int green = getIntAttribute(child, "green"); + int blue = getIntAttribute(child, "blue"); + if (red == green && red == blue) { + bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY; + bKGD_gray = red; + } else { + bKGD_red = red; + bKGD_green = green; + bKGD_blue = blue; + } + bKGD_present = true; + } +// } else if (childName.equals("ColorSpaceType")) { +// } else if (childName.equals("NumChannels")) { + + child = child.getNextSibling(); + } + } else if (name.equals("Compression")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("NumProgressiveScans")) { + // Use Adam7 if NumProgressiveScans > 1 + int scans = getIntAttribute(child, "value"); + IHDR_interlaceMethod = (scans > 1) ? 1 : 0; +// } else if (childName.equals("CompressionTypeName")) { +// } else if (childName.equals("Lossless")) { +// } else if (childName.equals("BitRate")) { + } + child = child.getNextSibling(); + } + } else if (name.equals("Data")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("BitsPerSample")) { + String s = getAttribute(child, "value"); + StringTokenizer t = new StringTokenizer(s); + int maxBits = -1; + while (t.hasMoreTokens()) { + int bits = Integer.parseInt(t.nextToken()); + if (bits > maxBits) { + maxBits = bits; + } + } + if (maxBits < 1) { + maxBits = 1; + } + if (maxBits == 3) maxBits = 4; + if (maxBits > 4 || maxBits < 8) { + maxBits = 8; + } + if (maxBits > 8) { + maxBits = 16; + } + IHDR_bitDepth = maxBits; + } else if (childName.equals("SignificantBitsPerSample")) { + String s = getAttribute(child, "value"); + StringTokenizer t = new StringTokenizer(s); + int numTokens = t.countTokens(); + if (numTokens == 1) { + sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY; + sBIT_grayBits = Integer.parseInt(t.nextToken()); + } else if (numTokens == 2) { + sBIT_colorType = + PNGImageReader.PNG_COLOR_GRAY_ALPHA; + sBIT_grayBits = Integer.parseInt(t.nextToken()); + sBIT_alphaBits = Integer.parseInt(t.nextToken()); + } else if (numTokens == 3) { + sBIT_colorType = PNGImageReader.PNG_COLOR_RGB; + sBIT_redBits = Integer.parseInt(t.nextToken()); + sBIT_greenBits = Integer.parseInt(t.nextToken()); + sBIT_blueBits = Integer.parseInt(t.nextToken()); + } else if (numTokens == 4) { + sBIT_colorType = + PNGImageReader.PNG_COLOR_RGB_ALPHA; + sBIT_redBits = Integer.parseInt(t.nextToken()); + sBIT_greenBits = Integer.parseInt(t.nextToken()); + sBIT_blueBits = Integer.parseInt(t.nextToken()); + sBIT_alphaBits = Integer.parseInt(t.nextToken()); + } + if (numTokens >= 1 && numTokens <= 4) { + sBIT_present = true; + } +// } else if (childName.equals("PlanarConfiguration")) { +// } else if (childName.equals("SampleFormat")) { +// } else if (childName.equals("SampleMSB")) { + } + child = child.getNextSibling(); + } + } else if (name.equals("Dimension")) { + boolean gotWidth = false; + boolean gotHeight = false; + boolean gotAspectRatio = false; + + float width = -1.0F; + float height = -1.0F; + float aspectRatio = -1.0F; + + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("PixelAspectRatio")) { + aspectRatio = getFloatAttribute(child, "value"); + gotAspectRatio = true; + } else if (childName.equals("HorizontalPixelSize")) { + width = getFloatAttribute(child, "value"); + gotWidth = true; + } else if (childName.equals("VerticalPixelSize")) { + height = getFloatAttribute(child, "value"); + gotHeight = true; +// } else if (childName.equals("ImageOrientation")) { +// } else if +// (childName.equals("HorizontalPhysicalPixelSpacing")) { +// } else if +// (childName.equals("VerticalPhysicalPixelSpacing")) { +// } else if (childName.equals("HorizontalPosition")) { +// } else if (childName.equals("VerticalPosition")) { +// } else if (childName.equals("HorizontalPixelOffset")) { +// } else if (childName.equals("VerticalPixelOffset")) { + } + child = child.getNextSibling(); + } + + if (gotWidth && gotHeight) { + pHYs_present = true; + pHYs_unitSpecifier = 1; + pHYs_pixelsPerUnitXAxis = (int)(width*1000 + 0.5F); + pHYs_pixelsPerUnitYAxis = (int)(height*1000 + 0.5F); + } else if (gotAspectRatio) { + pHYs_present = true; + pHYs_unitSpecifier = 0; + + // Find a reasonable rational approximation + int denom = 1; + for (; denom < 100; denom++) { + int num = (int)(aspectRatio*denom); + if (Math.abs(num/denom - aspectRatio) < 0.001) { + break; + } + } + pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom); + pHYs_pixelsPerUnitYAxis = denom; + } + } else if (name.equals("Document")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("ImageModificationTime")) { + tIME_present = true; + tIME_year = getIntAttribute(child, "year"); + tIME_month = getIntAttribute(child, "month"); + tIME_day = getIntAttribute(child, "day"); + tIME_hour = + getIntAttribute(child, "hour", 0, false); + tIME_minute = + getIntAttribute(child, "minute", 0, false); + tIME_second = + getIntAttribute(child, "second", 0, false); +// } else if (childName.equals("SubimageInterpretation")) { +// } else if (childName.equals("ImageCreationTime")) { + } + child = child.getNextSibling(); + } + } else if (name.equals("Text")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("TextEntry")) { + String keyword = + getAttribute(child, "keyword", "", false); + String value = getAttribute(child, "value"); + String language = + getAttribute(child, "language", "", false); + String compression = + getAttribute(child, "compression", "none", false); + + if (!isValidKeyword(keyword)) { + // Just ignore this node, PNG requires keywords + } else if (isISOLatin(value, true)) { + if (compression.equals("zip")) { + // Use a zTXt node + zTXt_keyword.add(keyword); + zTXt_text.add(value); + zTXt_compressionMethod.add(Integer.valueOf(0)); + } else { + // Use a tEXt node + tEXt_keyword.add(keyword); + tEXt_text.add(value); + } + } else { + // Use an iTXt node + iTXt_keyword.add(keyword); + iTXt_compressionFlag.add(Boolean.valueOf(compression.equals("zip"))); + iTXt_compressionMethod.add(Integer.valueOf(0)); + iTXt_languageTag.add(language); + iTXt_translatedKeyword.add(keyword); // fake it + iTXt_text.add(value); + } + } + child = child.getNextSibling(); + } +// } else if (name.equals("Transparency")) { +// Node child = node.getFirstChild(); +// while (child != null) { +// String childName = child.getNodeName(); +// if (childName.equals("Alpha")) { +// } else if (childName.equals("TransparentIndex")) { +// } else if (childName.equals("TransparentColor")) { +// } else if (childName.equals("TileTransparencies")) { +// } else if (childName.equals("TileOpacities")) { +// } +// child = child.getNextSibling(); +// } +// } else { +// // fatal(node, "Unknown child of root node!"); + } + + node = node.getNextSibling(); + } + } + + // Reset all instance variables to their initial state + public void reset() { + IHDR_present = false; + PLTE_present = false; + bKGD_present = false; + cHRM_present = false; + gAMA_present = false; + hIST_present = false; + iCCP_present = false; + iTXt_keyword = new ArrayList(); + iTXt_compressionFlag = new ArrayList(); + iTXt_compressionMethod = new ArrayList(); + iTXt_languageTag = new ArrayList(); + iTXt_translatedKeyword = new ArrayList(); + iTXt_text = new ArrayList(); + pHYs_present = false; + sBIT_present = false; + sPLT_present = false; + sRGB_present = false; + tEXt_keyword = new ArrayList(); + tEXt_text = new ArrayList(); + tIME_present = false; + tRNS_present = false; + zTXt_keyword = new ArrayList(); + zTXt_compressionMethod = new ArrayList(); + zTXt_text = new ArrayList(); + unknownChunkType = new ArrayList(); + unknownChunkData = new ArrayList(); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadataFormat.java new file mode 100644 index 000000000..b7d96dc3f --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadataFormat.java @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2001, 2004, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.ListResourceBundle; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class PNGMetadataFormat extends IIOMetadataFormatImpl { + + private static IIOMetadataFormat instance = null; + + private static String VALUE_0 = "0"; + private static String VALUE_1 = "1"; + private static String VALUE_12 = "12"; + private static String VALUE_23 = "23"; + private static String VALUE_31 = "31"; + private static String VALUE_59 = "59"; + private static String VALUE_60 = "60"; + private static String VALUE_255 = "255"; + private static String VALUE_MAX_16 = "65535"; // 2^16 - 1 + private static String VALUE_MAX_32 = "2147483647"; // 2^32 - 1 + + private PNGMetadataFormat() { + super(PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> IHDR + addElement("IHDR", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + addAttribute("IHDR", "width", + DATATYPE_INTEGER, true, null, + VALUE_1, VALUE_MAX_32, true, true); + + addAttribute("IHDR", "height", + DATATYPE_INTEGER, true, null, + VALUE_1, VALUE_MAX_32, true, true); + + addAttribute("IHDR", "bitDepth", + DATATYPE_INTEGER, true, null, + Arrays.asList(PNGMetadata.IHDR_bitDepths)); + + String[] colorTypes = { + "Grayscale", "RGB", "Palette", "GrayAlpha", "RGBAlpha" + }; + addAttribute("IHDR", "colorType", + DATATYPE_STRING, true, null, + Arrays.asList(colorTypes)); + + addAttribute("IHDR", "compressionMethod", + DATATYPE_STRING, true, null, + Arrays.asList(PNGMetadata.IHDR_compressionMethodNames)); + + addAttribute("IHDR", "filterMethod", + DATATYPE_STRING, true, null, + Arrays.asList(PNGMetadata.IHDR_filterMethodNames)); + + addAttribute("IHDR", "interlaceMethod", + DATATYPE_STRING, true, null, + Arrays.asList(PNGMetadata.IHDR_interlaceMethodNames)); + + // root -> PLTE + addElement("PLTE", PNGMetadata.nativeMetadataFormatName, + 1, 256); + + // root -> PLTE -> PLTEEntry + addElement("PLTEEntry", "PLTE", + CHILD_POLICY_EMPTY); + + addAttribute("PLTEEntry", "index", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("PLTEEntry", "red", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("PLTEEntry", "green", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("PLTEEntry", "blue", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> bKGD + addElement("bKGD", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_CHOICE); + + // root -> bKGD -> bKGD_Grayscale + addElement("bKGD_Grayscale", "bKGD", + CHILD_POLICY_EMPTY); + + addAttribute("bKGD_Grayscale", "gray", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + // root -> bKGD -> bKGD_RGB + addElement("bKGD_RGB", "bKGD", + CHILD_POLICY_EMPTY); + + addAttribute("bKGD_RGB", "red", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("bKGD_RGB", "green", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("bKGD_RGB", "blue", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + // root -> bKGD -> bKGD_Palette + addElement("bKGD_Palette", "bKGD", + CHILD_POLICY_EMPTY); + + addAttribute("bKGD_Palette", "index", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> cHRM + addElement("cHRM", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + addAttribute("cHRM", "whitePointX", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("cHRM", "whitePointY", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("cHRM", "redX", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("cHRM", "redY", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("cHRM", "greenX", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("cHRM", "greenY", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("cHRM", "blueX", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("cHRM", "blueY", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + // root -> gAMA + addElement("gAMA", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + addAttribute("gAMA", "value", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_32, true, true); + + // root -> hIST + addElement("hIST", PNGMetadata.nativeMetadataFormatName, + 1, 256); + + // root -> hISTEntry + addElement("hISTEntry", "hIST", + CHILD_POLICY_EMPTY); + + addAttribute("hISTEntry", "index", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("hISTEntry", "value", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + // root -> iCCP + addElement("iCCP", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + addAttribute("iCCP", "profileName", + DATATYPE_STRING, true, null); + + addAttribute("iCCP", "compressionMethod", + DATATYPE_STRING, true, null, + Arrays.asList(PNGMetadata.iCCP_compressionMethodNames)); + + addObjectValue("iCCP", byte.class, 0, Integer.MAX_VALUE); + + // root -> iTXt + addElement("iTXt", PNGMetadata.nativeMetadataFormatName, + 1, Integer.MAX_VALUE); + + // root -> iTXt -> iTXtEntry + addElement("iTXtEntry", "iTXt", + CHILD_POLICY_EMPTY); + + addAttribute("iTXtEntry", "keyword", + DATATYPE_STRING, true, null); + + addBooleanAttribute("iTXtEntry", "compressionFlag", + false, false); + + addAttribute("iTXtEntry", "compressionMethod", + DATATYPE_STRING, true, null); + + addAttribute("iTXtEntry", "languageTag", + DATATYPE_STRING, true, null); + + addAttribute("iTXtEntry", "translatedKeyword", + DATATYPE_STRING, true, null); + + addAttribute("iTXtEntry", "text", + DATATYPE_STRING, true, null); + + // root -> pHYS + addElement("pHYS", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + addAttribute("pHYS", "pixelsPerUnitXAxis", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_32, true, true); + addAttribute("pHYS", "pixelsPerUnitYAxis", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_32, true, true); + addAttribute("pHYS", "unitSpecifier", + DATATYPE_STRING, true, null, + Arrays.asList(PNGMetadata.unitSpecifierNames)); + + // root -> sBIT + addElement("sBIT", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_CHOICE); + + // root -> sBIT -> sBIT_Grayscale + addElement("sBIT_Grayscale", "sBIT", + CHILD_POLICY_EMPTY); + + addAttribute("sBIT_Grayscale", "gray", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> sBIT -> sBIT_GrayAlpha + addElement("sBIT_GrayAlpha", "sBIT", + CHILD_POLICY_EMPTY); + + addAttribute("sBIT_GrayAlpha", "gray", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sBIT_GrayAlpha", "alpha", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> sBIT -> sBIT_RGB + addElement("sBIT_RGB", "sBIT", + CHILD_POLICY_EMPTY); + + addAttribute("sBIT_RGB", "red", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sBIT_RGB", "green", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sBIT_RGB", "blue", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> sBIT -> sBIT_RGBAlpha + addElement("sBIT_RGBAlpha", "sBIT", + CHILD_POLICY_EMPTY); + + addAttribute("sBIT_RGBAlpha", "red", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sBIT_RGBAlpha", "green", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sBIT_RGBAlpha", "blue", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sBIT_RGBAlpha", "alpha", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> sBIT -> sBIT_Palette + addElement("sBIT_Palette", "sBIT", + CHILD_POLICY_EMPTY); + + addAttribute("sBIT_Palette", "red", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sBIT_Palette", "green", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sBIT_Palette", "blue", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> sPLT + addElement("sPLT", PNGMetadata.nativeMetadataFormatName, + 1, 256); + + // root -> sPLT -> sPLTEntry + addElement("sPLTEntry", "sPLT", + CHILD_POLICY_EMPTY); + + addAttribute("sPLTEntry", "index", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sPLTEntry", "red", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sPLTEntry", "green", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sPLTEntry", "blue", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("sPLTEntry", "alpha", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> sRGB + addElement("sRGB", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + addAttribute("sRGB", "renderingIntent", + DATATYPE_STRING, true, null, + Arrays.asList(PNGMetadata.renderingIntentNames)); + + // root -> tEXt + addElement("tEXt", PNGMetadata.nativeMetadataFormatName, + 1, Integer.MAX_VALUE); + + // root -> tEXt -> tEXtEntry + addElement("tEXtEntry", "tEXt", + CHILD_POLICY_EMPTY); + + addAttribute("tEXtEntry", "keyword", + DATATYPE_STRING, true, null); + + addAttribute("tEXtEntry", "value", + DATATYPE_STRING, true, null); + + // root -> tIME + addElement("tIME", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + addAttribute("tIME", "year", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("tIME", "month", + DATATYPE_INTEGER, true, null, + VALUE_1, VALUE_12, true, true); + + addAttribute("tIME", "day", + DATATYPE_INTEGER, true, null, + VALUE_1, VALUE_31, true, true); + + addAttribute("tIME", "hour", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_23, true, true); + + addAttribute("tIME", "minute", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_59, true, true); + + addAttribute("tIME", "second", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_60, true, true); + + // root -> tRNS + addElement("tRNS", PNGMetadata.nativeMetadataFormatName, + CHILD_POLICY_CHOICE); + + // root -> tRNS -> tRNS_Grayscale + addElement("tRNS_Grayscale", "tRNS", + CHILD_POLICY_EMPTY); + + addAttribute("tRNS_Grayscale", "gray", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + // root -> tRNS -> tRNS_RGB + addElement("tRNS_RGB", "tRNS", + CHILD_POLICY_EMPTY); + + addAttribute("tRNS_RGB", "red", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("tRNS_RGB", "green", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + addAttribute("tRNS_RGB", "blue", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_MAX_16, true, true); + + // root -> tRNS -> tRNS_Palette + addElement("tRNS_Palette", "tRNS", + CHILD_POLICY_EMPTY); + + addAttribute("tRNS_Palette", "index", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + addAttribute("tRNS_Palette", "alpha", + DATATYPE_INTEGER, true, null, + VALUE_0, VALUE_255, true, true); + + // root -> zTXt + addElement("zTXt", PNGMetadata.nativeMetadataFormatName, + 1, Integer.MAX_VALUE); + + // root -> zTXt -> zTXtEntry + addElement("zTXtEntry", "zTXt", + CHILD_POLICY_EMPTY); + + addAttribute("zTXtEntry", "keyword", + DATATYPE_STRING, true, null); + + addAttribute("zTXtEntry", "compressionMethod", + DATATYPE_STRING, true, null, + Arrays.asList(PNGMetadata.zTXt_compressionMethodNames)); + + addAttribute("zTXtEntry", "text", + DATATYPE_STRING, true, null); + + // root -> UnknownChunks + addElement("UnknownChunks", PNGMetadata.nativeMetadataFormatName, + 1, Integer.MAX_VALUE); + + // root -> UnknownChunks -> UnknownChunk + addElement("UnknownChunk", "UnknownChunks", + CHILD_POLICY_EMPTY); + + addAttribute("UnknownChunk", "type", + DATATYPE_STRING, true, null); + + addObjectValue("UnknownChunk", byte.class, 0, Integer.MAX_VALUE); + } + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + return true; + } + + public static synchronized IIOMetadataFormat getInstance() { + if (instance == null) { + instance = new PNGMetadataFormat(); + } + return instance; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadataFormatResources.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadataFormatResources.java new file mode 100644 index 000000000..d99946bd8 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/PNGMetadataFormatResources.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2001, 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +import java.util.ListResourceBundle; + +public class PNGMetadataFormatResources extends ListResourceBundle { + + public PNGMetadataFormatResources() {} + + protected Object[][] getContents() { + return new Object[][] { + + // Node name, followed by description + { "IHDR", "The IHDR chunk, containing the header" }, + { "PLTE", "The PLTE chunk, containing the palette" }, + { "PLTEEntry", "A palette entry" }, + { "bKGD", "The bKGD chunk, containing the background color" }, + { "bKGD_RGB", "An RGB background color, for RGB and RGBAlpha images" }, + { "bKGD_Grayscale", + "A grayscale background color, for Gray and GrayAlpha images" }, + { "bKGD_Palette", "A background palette index" }, + { "cHRM", "The cHRM chunk, containing color calibration" }, + { "gAMA", "The gAMA chunk, containing the image gamma" }, + { "hIST", "The hIST chunk, containing histogram information " }, + { "hISTEntry", "A histogram entry" }, + { "iCCP", "The iCCP chunk, containing an ICC color profile" }, + { "iTXt", "The iTXt chunk, containing internationalized text" }, + { "iTXtEntry", "A localized text entry" }, + { "pHYS", + "The pHYS chunk, containing the pixel size and aspect ratio" }, + { "sBIT", "The sBIT chunk, containing significant bit information" }, + { "sBIT_Grayscale", "Significant bit information for gray samples" }, + { "sBIT_GrayAlpha", + "Significant bit information for gray and alpha samples" }, + { "sBIT_RGB", "Significant bit information for RGB samples" }, + { "sBIT_RGBAlpha", "Significant bit information for RGBA samples" }, + { "sBIT_Palette", + "Significant bit information for RGB palette entries" }, + { "sPLT", "The sPLT chunk, containing a suggested palette" }, + { "sPLTEntry", "A suggested palette entry" }, + { "sRGB", "The sRGB chunk, containing rendering intent information" }, + { "tEXt", "The tEXt chunk, containing text" }, + { "tEXtEntry", "A text entry" }, + { "tIME", "The tIME chunk, containing the image modification time" }, + { "tRNS", "The tRNS chunk, containing transparency information" }, + { "tRNS_Grayscale", + "A grayscale value that should be considered transparent" }, + { "tRNS_RGB", + "An RGB value that should be considered transparent" }, + { "tRNS_Palette", + "A palette index that should be considered transparent" }, + { "zTXt", "The zTXt chunk, containing compressed text" }, + { "zTXtEntry", "A compressed text entry" }, + { "UnknownChunks", "A set of unknown chunks" }, + { "UnknownChunk", "Unknown chunk data stored as a byte array" }, + + // Node name + "/" + AttributeName, followed by description + { "IHDR/width", "The width of the image in pixels" }, + { "IHDR/height", "The height of the image in pixels" }, + { "IHDR/bitDepth", "The bit depth of the image samples" }, + { "IHDR/colorType", "The color type of the image" }, + { "IHDR/compressionMethod", +"The compression used for image data, always \"deflate\"" }, + { "IHDR/filterMethod", +"The filtering method used for compression, always \"adaptive\"" }, + { "IHDR/interlaceMethod", + "The interlacing method, \"none\" or \"adam7\"" }, + + { "PLTEEntry/index", "The index of a palette entry" }, + { "PLTEEntry/red", "The red value of a palette entry" }, + { "PLTEEntry/green", "The green value of a palette entry" }, + { "PLTEEntry/blue", "The blue value of a palette entry" }, + + { "bKGD_Grayscale/gray", "A gray value to be used as a background" }, + { "bKGD_RGB/red", "A red value to be used as a background" }, + { "bKGD_RGB/green", "A green value to be used as a background" }, + { "bKGD_RGB/blue", "A blue value to be used as a background" }, + { "bKGD_Palette/index", "A palette index to be used as a background" }, + + { "cHRM/whitePointX", + "The CIE x coordinate of the white point, multiplied by 1e5" }, + { "cHRM/whitePointY", + "The CIE y coordinate of the white point, multiplied by 1e5" }, + { "cHRM/redX", + "The CIE x coordinate of the red primary, multiplied by 1e5" }, + { "cHRM/redY", + "The CIE y coordinate of the red primary, multiplied by 1e5" }, + { "cHRM/greenX", + "The CIE x coordinate of the green primary, multiplied by 1e5" }, + { "cHRM/greenY", + "The CIE y coordinate of the green primary, multiplied by 1e5" }, + { "cHRM/blueX", + "The CIE x coordinate of the blue primary, multiplied by 1e5" }, + { "cHRM/blueY", + "The CIE y coordinate of the blue primary, multiplied by 1e5" }, + + { "gAMA/value", + "The image gamma, multiplied by 1e5" }, + + { "hISTEntry/index", "The palette index of this histogram entry" }, + { "hISTEntry/value", "The frequency of this histogram entry" }, + + { "iCCP/profileName", "The name of this ICC profile" }, + { "iCCP/compressionMethod", + "The compression method used to store this ICC profile" }, + + { "iTXtEntry/keyword", "The keyword" }, + { "iTXtEntry/compressionMethod", + "The compression method used to store this iTXt entry" }, + { "iTXtEntry/languageTag", + "The ISO tag describing the language this iTXt entry" }, + { "iTXtEntry/translatedKeyword", + "The translated keyword for iTXt entry" }, + { "iTXtEntry/text", + "The localized text" }, + + { "pHYS/pixelsPerUnitXAxis", + "The number of horizontal pixels per unit, multiplied by 1e5" }, + { "pHYS/pixelsPerUnitYAxis", + "The number of vertical pixels per unit, multiplied by 1e5" }, + { "pHYS/unitSpecifier", + "The unit specifier for this chunk (i.e., meters)" }, + + { "sBIT_Grayscale/gray", + "The number of significant bits of the gray samples" }, + { "sBIT_GrayAlpha/gray", + "The number of significant bits of the gray samples" }, + { "sBIT_GrayAlpha/alpha", + "The number of significant bits of the alpha samples" }, + { "sBIT_RGB/red", + "The number of significant bits of the red samples" }, + { "sBIT_RGB/green", + "The number of significant bits of the green samples" }, + { "sBIT_RGB/blue", + "The number of significant bits of the blue samples" }, + { "sBIT_RGBAlpha/red", + "The number of significant bits of the red samples" }, + { "sBIT_RGBAlpha/green", + "The number of significant bits of the green samples" }, + { "sBIT_RGBAlpha/blue", + "The number of significant bits of the blue samples" }, + { "sBIT_RGBAlpha/alpha", + "The number of significant bits of the alpha samples" }, + { "sBIT_Palette/red", + "The number of significant bits of the red palette entries" }, + { "sBIT_Palette/green", + "The number of significant bits of the green palette entries" }, + { "sBIT_Palette/blue", + "The number of significant bits of the blue palette entries" }, + + { "sPLTEntry/index", "The index of a suggested palette entry" }, + { "sPLTEntry/red", "The red value of a suggested palette entry" }, + { "sPLTEntry/green", "The green value of a suggested palette entry" }, + { "sPLTEntry/blue", "The blue value of a suggested palette entry" }, + { "sPLTEntry/alpha", "The blue value of a suggested palette entry" }, + + { "sRGB/renderingIntent", "The rendering intent" }, + + { "tEXtEntry/keyword", "The keyword" }, + { "tEXtEntry/value", "The text" }, + + { "tIME/year", "The year when the image was last modified" }, + { "tIME/month", + "The month when the image was last modified, 1 = January" }, + { "tIME/day", + "The day of the month when the image was last modified" }, + { "tIME/hour", + "The hour when the image was last modified" }, + { "tIME/minute", + "The minute when the image was last modified" }, + { "tIME/second", + "The second when the image was last modified, 60 = leap second" }, + + { "tRNS_Grayscale/gray", + "The gray value to be considered transparent" }, + { "tRNS_RGB/red", + "The red value to be considered transparent" }, + { "tRNS_RGB/green", + "The green value to be considered transparent" }, + { "tRNS_RGB/blue", + "The blure value to be considered transparent" }, + { "tRNS_Palette/index", + "A palette index to be considered transparent" }, + { "tRNS_Palette/alpha", + "The transparency associated with the palette entry" }, + + { "zTXtEntry/keyword", "The keyword" }, + { "zTXtEntry/compressionMethod", "The compression method" }, + { "zTXtEntry/text", "The compressed text" }, + + { "UnknownChunk/type", "The 4-character type of the unknown chunk" } + + }; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/RowFilter.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/RowFilter.java new file mode 100644 index 000000000..63ac14b49 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/png/RowFilter.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2000, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.png; + +public class RowFilter { + + private static final int abs(int x) { + return (x < 0) ? -x : x; + } + + // Returns the sum of absolute differences + protected static int subFilter(byte[] currRow, + byte[] subFilteredRow, + int bytesPerPixel, + int bytesPerRow) { + int badness = 0; + for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) { + int curr = currRow[i] & 0xff; + int left = currRow[i - bytesPerPixel] & 0xff; + int difference = curr - left; + subFilteredRow[i] = (byte)difference; + + badness += abs(difference); + } + + return badness; + } + + // Returns the sum of absolute differences + protected static int upFilter(byte[] currRow, + byte[] prevRow, + byte[] upFilteredRow, + int bytesPerPixel, + int bytesPerRow) { + int badness = 0; + for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) { + int curr = currRow[i] & 0xff; + int up = prevRow[i] & 0xff; + int difference = curr - up; + upFilteredRow[i] = (byte)difference; + + badness += abs(difference); + } + + return badness; + } + + protected final int paethPredictor(int a, int b, int c) { + int p = a + b - c; + int pa = abs(p - a); + int pb = abs(p - b); + int pc = abs(p - c); + + if ((pa <= pb) && (pa <= pc)) { + return a; + } else if (pb <= pc) { + return b; + } else { + return c; + } + } + + public int filterRow(int colorType, + byte[] currRow, + byte[] prevRow, + byte[][] scratchRows, + int bytesPerRow, + int bytesPerPixel) { + + // Use type 0 for palette images + if (colorType != PNGImageReader.PNG_COLOR_PALETTE) { + System.arraycopy(currRow, bytesPerPixel, + scratchRows[0], bytesPerPixel, + bytesPerRow); + return 0; + } + + int[] filterBadness = new int[5]; + for (int i = 0; i < 5; i++) { + filterBadness[i] = Integer.MAX_VALUE; + } + + { + int badness = 0; + + for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) { + int curr = currRow[i] & 0xff; + badness += curr; + } + + filterBadness[0] = badness; + } + + { + byte[] subFilteredRow = scratchRows[1]; + int badness = subFilter(currRow, + subFilteredRow, + bytesPerPixel, + bytesPerRow); + + filterBadness[1] = badness; + } + + { + byte[] upFilteredRow = scratchRows[2]; + int badness = upFilter(currRow, + prevRow, + upFilteredRow, + bytesPerPixel, + bytesPerRow); + + filterBadness[2] = badness; + } + + { + byte[] averageFilteredRow = scratchRows[3]; + int badness = 0; + + for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) { + int curr = currRow[i] & 0xff; + int left = currRow[i - bytesPerPixel] & 0xff; + int up = prevRow[i] & 0xff; + int difference = curr - (left + up)/2;; + averageFilteredRow[i] = (byte)difference; + + badness += abs(difference); + } + + filterBadness[3] = badness; + } + + { + byte[] paethFilteredRow = scratchRows[4]; + int badness = 0; + + for (int i = bytesPerPixel; i < bytesPerRow + bytesPerPixel; i++) { + int curr = currRow[i] & 0xff; + int left = currRow[i - bytesPerPixel] & 0xff; + int up = prevRow[i] & 0xff; + int upleft = prevRow[i - bytesPerPixel] & 0xff; + int predictor = paethPredictor(left, up, upleft); + int difference = curr - predictor; + paethFilteredRow[i] = (byte)difference; + + badness += abs(difference); + } + + filterBadness[4] = badness; + } + + int minBadness = filterBadness[0]; + int filterType = 0; + + for (int i = 1; i < 5; i++) { + if (filterBadness[i] < minBadness) { + minBadness = filterBadness[i]; + filterType = i; + } + } + + if (filterType == 0) { + System.arraycopy(currRow, bytesPerPixel, + scratchRows[0], bytesPerPixel, + bytesPerRow); + } + + return filterType; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageReader.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageReader.java new file mode 100644 index 000000000..22377d776 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageReader.java @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2003, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.wbmp; + +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +import javax.imageio.IIOException; +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; + +import java.io.*; +import java.util.ArrayList; +import java.util.Iterator; + +import com.sun.imageio.plugins.common.I18N; +import com.sun.imageio.plugins.common.ReaderUtil; + +/** This class is the Java Image IO plugin reader for WBMP images. + * It may subsample the image, clip the image, + * and shift the decoded image origin if the proper decoding parameter + * are set in the provided WBMPImageReadParam. + */ +public class WBMPImageReader extends ImageReader { + /** The input stream where reads from */ + private ImageInputStream iis = null; + + /** Indicates whether the header is read. */ + private boolean gotHeader = false; + + /** The original image width. */ + private int width; + + /** The original image height. */ + private int height; + + private int wbmpType; + + private WBMPMetadata metadata; + + /** Constructs WBMPImageReader from the provided + * ImageReaderSpi. + */ + public WBMPImageReader(ImageReaderSpi originator) { + super(originator); + } + + /** Overrides the method defined in the superclass. */ + public void setInput(Object input, + boolean seekForwardOnly, + boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + iis = (ImageInputStream) input; // Always works + gotHeader = false; + } + + /** Overrides the method defined in the superclass. */ + public int getNumImages(boolean allowSearch) throws IOException { + if (iis == null) { + throw new IllegalStateException(I18N.getString("GetNumImages0")); + } + if (seekForwardOnly && allowSearch) { + throw new IllegalStateException(I18N.getString("GetNumImages1")); + } + return 1; + } + + public int getWidth(int imageIndex) throws IOException { + checkIndex(imageIndex); + readHeader(); + return width; + } + + public int getHeight(int imageIndex) throws IOException { + checkIndex(imageIndex); + readHeader(); + return height; + } + + public boolean isRandomAccessEasy(int imageIndex) throws IOException { + checkIndex(imageIndex); + return true; + } + + private void checkIndex(int imageIndex) { + if (imageIndex != 0) { + throw new IndexOutOfBoundsException(I18N.getString("WBMPImageReader0")); + } + } + + public void readHeader() throws IOException { + if (gotHeader) + return; + + if (iis == null) { + throw new IllegalStateException("Input source not set!"); + } + + metadata = new WBMPMetadata(); + + wbmpType = iis.readByte(); // TypeField + byte fixHeaderField = iis.readByte(); + + // check for valid wbmp image + if (fixHeaderField != 0 + || !isValidWbmpType(wbmpType)) + { + throw new IIOException(I18N.getString("WBMPImageReader2")); + } + + metadata.wbmpType = wbmpType; + + // Read image width + width = ReaderUtil.readMultiByteInteger(iis); + metadata.width = width; + + // Read image height + height = ReaderUtil.readMultiByteInteger(iis); + metadata.height = height; + + gotHeader = true; + } + + public Iterator getImageTypes(int imageIndex) + throws IOException { + checkIndex(imageIndex); + readHeader(); + + BufferedImage bi = + new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY); + ArrayList list = new ArrayList(1); + list.add(new ImageTypeSpecifier(bi)); + return list.iterator(); + } + + public ImageReadParam getDefaultReadParam() { + return new ImageReadParam(); + } + + public IIOMetadata getImageMetadata(int imageIndex) + throws IOException { + checkIndex(imageIndex); + if (metadata == null) { + readHeader(); + } + return metadata; + } + + public IIOMetadata getStreamMetadata() throws IOException { + return null; + } + + public BufferedImage read(int imageIndex, ImageReadParam param) + throws IOException { + + if (iis == null) { + throw new IllegalStateException(I18N.getString("WBMPImageReader1")); + } + + checkIndex(imageIndex); + clearAbortRequest(); + processImageStarted(imageIndex); + if (param == null) + param = getDefaultReadParam(); + + //read header + readHeader(); + + Rectangle sourceRegion = new Rectangle(0, 0, 0, 0); + Rectangle destinationRegion = new Rectangle(0, 0, 0, 0); + + computeRegions(param, this.width, this.height, + param.getDestination(), + sourceRegion, + destinationRegion); + + int scaleX = param.getSourceXSubsampling(); + int scaleY = param.getSourceYSubsampling(); + int xOffset = param.getSubsamplingXOffset(); + int yOffset = param.getSubsamplingYOffset(); + + // If the destination is provided, then use it. Otherwise, create new one + BufferedImage bi = param.getDestination(); + + if (bi == null) + bi = new BufferedImage(destinationRegion.x + destinationRegion.width, + destinationRegion.y + destinationRegion.height, + BufferedImage.TYPE_BYTE_BINARY); + + boolean noTransform = + destinationRegion.equals(new Rectangle(0, 0, width, height)) && + destinationRegion.equals(new Rectangle(0, 0, bi.getWidth(), bi.getHeight())); + + // Get the image data. + WritableRaster tile = bi.getWritableTile(0, 0); + + // Get the SampleModel. + MultiPixelPackedSampleModel sm = + (MultiPixelPackedSampleModel)bi.getSampleModel(); + + if (noTransform) { + if (abortRequested()) { + processReadAborted(); + return bi; + } + + // If noTransform is necessary, read the data. + iis.read(((DataBufferByte)tile.getDataBuffer()).getData(), + 0, height*sm.getScanlineStride()); + processImageUpdate(bi, + 0, 0, + width, height, 1, 1, + new int[]{0}); + processImageProgress(100.0F); + } else { + int len = (this.width + 7) / 8; + byte[] buf = new byte[len]; + byte[] data = ((DataBufferByte)tile.getDataBuffer()).getData(); + int lineStride = sm.getScanlineStride(); + iis.skipBytes(len * sourceRegion.y); + int skipLength = len * (scaleY - 1); + + // cache the values to avoid duplicated computation + int[] srcOff = new int[destinationRegion.width]; + int[] destOff = new int[destinationRegion.width]; + int[] srcPos = new int[destinationRegion.width]; + int[] destPos = new int[destinationRegion.width]; + + for (int i = destinationRegion.x, x = sourceRegion.x, j = 0; + i < destinationRegion.x + destinationRegion.width; + i++, j++, x += scaleX) { + srcPos[j] = x >> 3; + srcOff[j] = 7 - (x & 7); + destPos[j] = i >> 3; + destOff[j] = 7 - (i & 7); + } + + for (int j = 0, y = sourceRegion.y, + k = destinationRegion.y * lineStride; + j < destinationRegion.height; j++, y+=scaleY) { + + if (abortRequested()) + break; + iis.read(buf, 0, len); + for (int i = 0; i < destinationRegion.width; i++) { + //get the bit and assign to the data buffer of the raster + int v = (buf[srcPos[i]] >> srcOff[i]) & 1; + data[k + destPos[i]] |= v << destOff[i]; + } + + k += lineStride; + iis.skipBytes(skipLength); + processImageUpdate(bi, + 0, j, + destinationRegion.width, 1, 1, 1, + new int[]{0}); + processImageProgress(100.0F*j/destinationRegion.height); + } + } + + if (abortRequested()) + processReadAborted(); + else + processImageComplete(); + return bi; + } + + public boolean canReadRaster() { + return true; + } + + public Raster readRaster(int imageIndex, + ImageReadParam param) throws IOException { + BufferedImage bi = read(imageIndex, param); + return bi.getData(); + } + + public void reset() { + super.reset(); + iis = null; + gotHeader = false; + } + + /* + * This method verifies that given byte is valid wbmp type marker. + * At the moment only 0x0 marker is described by wbmp spec. + */ + boolean isValidWbmpType(int type) { + return type == 0; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageReaderSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageReaderSpi.java new file mode 100644 index 000000000..45c5b372c --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageReaderSpi.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.wbmp; + +import java.util.Locale; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ServiceRegistry; +import java.io.IOException; +import javax.imageio.ImageReader; +import javax.imageio.IIOException; +import com.sun.imageio.plugins.common.ReaderUtil; + +public class WBMPImageReaderSpi extends ImageReaderSpi { + + private static final int MAX_WBMP_WIDTH = 1024; + private static final int MAX_WBMP_HEIGHT = 768; + + private static String [] writerSpiNames = + {"com.sun.imageio.plugins.wbmp.WBMPImageWriterSpi"}; + private static String[] formatNames = {"wbmp", "WBMP"}; + private static String[] entensions = {"wbmp"}; + private static String[] mimeType = {"image/vnd.wap.wbmp"}; + + private boolean registered = false; + + public WBMPImageReaderSpi() { + super("Oracle Corporation", + "1.0", + formatNames, + entensions, + mimeType, + "com.sun.imageio.plugins.wbmp.WBMPImageReader", + new Class[] { ImageInputStream.class }, + writerSpiNames, + true, + null, null, null, null, + true, + WBMPMetadata.nativeMetadataFormatName, + "com.sun.imageio.plugins.wbmp.WBMPMetadataFormat", + null, null); + } + + public void onRegistration(ServiceRegistry registry, + Class category) { + if (registered) { + return; + } + registered = true; + } + + public String getDescription(Locale locale) { + return "Standard WBMP Image Reader"; + } + + public boolean canDecodeInput(Object source) throws IOException { + if (!(source instanceof ImageInputStream)) { + return false; + } + + ImageInputStream stream = (ImageInputStream)source; + + stream.mark(); + try { + int type = stream.readByte(); // TypeField + int fixHeaderField = stream.readByte(); + // check WBMP "header" + if (type != 0 || fixHeaderField != 0) { + // while WBMP reader does not support ext WBMP headers + return false; + } + + int width = ReaderUtil.readMultiByteInteger(stream); + int height = ReaderUtil.readMultiByteInteger(stream); + // check image dimension + if (width <= 0 || height <= 0) { + return false; + } + + long dataLength = stream.length(); + if (dataLength == -1) { + // We can't verify that amount of data in the stream + // corresponds to image dimension because we do not know + // the length of the data stream. + // Assuming that wbmp image are used for mobile devices, + // let's introduce an upper limit for image dimension. + // In case if exact amount of raster data is unknown, + // let's reject images with dimension above the limit. + return (width < MAX_WBMP_WIDTH) && (height < MAX_WBMP_HEIGHT); + } + + dataLength -= stream.getStreamPosition(); + + long scanSize = (width / 8) + ((width % 8) == 0 ? 0 : 1); + + return (dataLength == scanSize * height); + } finally { + stream.reset(); + } + } + + public ImageReader createReaderInstance(Object extension) + throws IIOException { + return new WBMPImageReader(this); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageWriter.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageWriter.java new file mode 100644 index 000000000..45d3187b2 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageWriter.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2003, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.wbmp; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; + +import java.io.IOException; + +import javax.imageio.IIOImage; +import javax.imageio.IIOException; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; + +import com.sun.imageio.plugins.common.I18N; + +/** + * The Java Image IO plugin writer for encoding a binary RenderedImage into + * a WBMP format. + * + * The encoding process may clip, subsample using the parameters + * specified in the ImageWriteParam. + * + * @see com.sun.media.imageio.plugins.WBMPImageWriteParam + */ +public class WBMPImageWriter extends ImageWriter { + /** The output stream to write into */ + private ImageOutputStream stream = null; + + // Get the number of bits required to represent an int. + private static int getNumBits(int intValue) { + int numBits = 32; + int mask = 0x80000000; + while(mask != 0 && (intValue & mask) == 0) { + numBits--; + mask >>>= 1; + } + return numBits; + } + + // Convert an int value to WBMP multi-byte format. + private static byte[] intToMultiByte(int intValue) { + int numBitsLeft = getNumBits(intValue); + byte[] multiBytes = new byte[(numBitsLeft + 6)/7]; + + int maxIndex = multiBytes.length - 1; + for(int b = 0; b <= maxIndex; b++) { + multiBytes[b] = (byte)((intValue >>> ((maxIndex - b)*7))&0x7f); + if(b != maxIndex) { + multiBytes[b] |= (byte)0x80; + } + } + + return multiBytes; + } + + /** Constructs WBMPImageWriter based on the provided + * ImageWriterSpi. + */ + public WBMPImageWriter(ImageWriterSpi originator) { + super(originator); + } + + public void setOutput(Object output) { + super.setOutput(output); // validates output + if (output != null) { + if (!(output instanceof ImageOutputStream)) + throw new IllegalArgumentException(I18N.getString("WBMPImageWriter")); + this.stream = (ImageOutputStream)output; + } else + this.stream = null; + } + + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { + return null; + } + + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param) { + WBMPMetadata meta = new WBMPMetadata(); + meta.wbmpType = 0; // default wbmp level + return meta; + } + + public IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param) { + return null; + } + + public IIOMetadata convertImageMetadata(IIOMetadata metadata, + ImageTypeSpecifier type, + ImageWriteParam param) { + return null; + } + + public boolean canWriteRasters() { + return true; + } + + public void write(IIOMetadata streamMetadata, + IIOImage image, + ImageWriteParam param) throws IOException { + + if (stream == null) { + throw new IllegalStateException(I18N.getString("WBMPImageWriter3")); + } + + if (image == null) { + throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4")); + } + + clearAbortRequest(); + processImageStarted(0); + if (param == null) + param = getDefaultWriteParam(); + + RenderedImage input = null; + Raster inputRaster = null; + boolean writeRaster = image.hasRaster(); + Rectangle sourceRegion = param.getSourceRegion(); + SampleModel sampleModel = null; + + if (writeRaster) { + inputRaster = image.getRaster(); + sampleModel = inputRaster.getSampleModel(); + } else { + input = image.getRenderedImage(); + sampleModel = input.getSampleModel(); + + inputRaster = input.getData(); + } + + checkSampleModel(sampleModel); + if (sourceRegion == null) + sourceRegion = inputRaster.getBounds(); + else + sourceRegion = sourceRegion.intersection(inputRaster.getBounds()); + + if (sourceRegion.isEmpty()) + throw new RuntimeException(I18N.getString("WBMPImageWriter1")); + + int scaleX = param.getSourceXSubsampling(); + int scaleY = param.getSourceYSubsampling(); + int xOffset = param.getSubsamplingXOffset(); + int yOffset = param.getSubsamplingYOffset(); + + sourceRegion.translate(xOffset, yOffset); + sourceRegion.width -= xOffset; + sourceRegion.height -= yOffset; + + int minX = sourceRegion.x / scaleX; + int minY = sourceRegion.y / scaleY; + int w = (sourceRegion.width + scaleX - 1) / scaleX; + int h = (sourceRegion.height + scaleY - 1) / scaleY; + + Rectangle destinationRegion = new Rectangle(minX, minY, w, h); + sampleModel = sampleModel.createCompatibleSampleModel(w, h); + + SampleModel destSM= sampleModel; + + // If the data are not formatted nominally then reformat. + if(sampleModel.getDataType() != DataBuffer.TYPE_BYTE || + !(sampleModel instanceof MultiPixelPackedSampleModel) || + ((MultiPixelPackedSampleModel)sampleModel).getDataBitOffset() != 0) { + destSM = + new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, + w, h, 1, + w + 7 >> 3, 0); + } + + if (!destinationRegion.equals(sourceRegion)) { + if (scaleX == 1 && scaleY == 1) + inputRaster = inputRaster.createChild(inputRaster.getMinX(), + inputRaster.getMinY(), + w, h, minX, minY, null); + else { + WritableRaster ras = Raster.createWritableRaster(destSM, + new Point(minX, minY)); + + byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData(); + + for(int j = minY, y = sourceRegion.y, k = 0; + j < minY + h; j++, y += scaleY) { + + for (int i = 0, x = sourceRegion.x; + i > 3)] |= v << (7 - (i & 7)); + } + k += w + 7 >> 3; + } + inputRaster = ras; + } + } + + // If the data are not formatted nominally then reformat. + if(!destSM.equals(inputRaster.getSampleModel())) { + WritableRaster raster = + Raster.createWritableRaster(destSM, + new Point(inputRaster.getMinX(), + inputRaster.getMinY())); + raster.setRect(inputRaster); + inputRaster = raster; + } + + // Check whether the image is white-is-zero. + boolean isWhiteZero = false; + if(!writeRaster && input.getColorModel() instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)input.getColorModel(); + isWhiteZero = icm.getRed(0) > icm.getRed(1); + } + + // Get the line stride, bytes per row, and data array. + int lineStride = + ((MultiPixelPackedSampleModel)destSM).getScanlineStride(); + int bytesPerRow = (w + 7)/8; + byte[] bdata = ((DataBufferByte)inputRaster.getDataBuffer()).getData(); + + // Write WBMP header. + stream.write(0); // TypeField + stream.write(0); // FixHeaderField + stream.write(intToMultiByte(w)); // width + stream.write(intToMultiByte(h)); // height + + // Write the data. + if(!isWhiteZero && lineStride == bytesPerRow) { + // Write the entire image. + stream.write(bdata, 0, h * bytesPerRow); + processImageProgress(100.0F); + } else { + // Write the image row-by-row. + int offset = 0; + if(!isWhiteZero) { + // Black-is-zero + for(int row = 0; row < h; row++) { + if (abortRequested()) + break; + stream.write(bdata, offset, bytesPerRow); + offset += lineStride; + processImageProgress(100.0F * row / h); + } + } else { + // White-is-zero: need to invert data. + byte[] inverted = new byte[bytesPerRow]; + for(int row = 0; row < h; row++) { + if (abortRequested()) + break; + for(int col = 0; col < bytesPerRow; col++) { + inverted[col] = (byte)(~(bdata[col+offset])); + } + stream.write(inverted, 0, bytesPerRow); + offset += lineStride; + processImageProgress(100.0F * row / h); + } + } + } + + if (abortRequested()) + processWriteAborted(); + else { + processImageComplete(); + stream.flushBefore(stream.getStreamPosition()); + } + } + + public void reset() { + super.reset(); + stream = null; + } + + private void checkSampleModel(SampleModel sm) { + int type = sm.getDataType(); + if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT + || sm.getNumBands() != 1 || sm.getSampleSize(0) != 1) + throw new IllegalArgumentException(I18N.getString("WBMPImageWriter2")); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageWriterSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageWriterSpi.java new file mode 100644 index 000000000..5b18f0ee2 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPImageWriterSpi.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.wbmp; + +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.spi.IIORegistry; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.ImageWriter; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.IIOException; + +import java.awt.image.ColorModel; +import java.awt.image.IndexColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.SampleModel; +import java.util.Locale; + +public class WBMPImageWriterSpi extends ImageWriterSpi { + private static String [] readerSpiNames = + {"com.sun.imageio.plugins.wbmp.WBMPImageReaderSpi"}; + private static String[] formatNames = {"wbmp", "WBMP"}; + private static String[] entensions = {"wbmp"}; + private static String[] mimeType = {"image/vnd.wap.wbmp"}; + + private boolean registered = false; + + public WBMPImageWriterSpi() { + super("Oracle Corporation", + "1.0", + formatNames, + entensions, + mimeType, + "com.sun.imageio.plugins.wbmp.WBMPImageWriter", + new Class[] { ImageOutputStream.class }, + readerSpiNames, + true, + null, null, null, null, + true, + null, null, null, null); + } + + public String getDescription(Locale locale) { + return "Standard WBMP Image Writer"; + } + + public void onRegistration(ServiceRegistry registry, + Class category) { + if (registered) { + return; + } + + registered = true; + } + + public boolean canEncodeImage(ImageTypeSpecifier type) { + SampleModel sm = type.getSampleModel(); + if (!(sm instanceof MultiPixelPackedSampleModel)) + return false; + if (sm.getSampleSize(0) != 1) + return false; + + return true; + } + + public ImageWriter createWriterInstance(Object extension) + throws IIOException { + return new WBMPImageWriter(this); + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPMetadata.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPMetadata.java new file mode 100644 index 000000000..c28046f0b --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPMetadata.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2003, 2004, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.wbmp; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import org.w3c.dom.Node; +import com.sun.imageio.plugins.common.I18N; + +import com.sun.imageio.plugins.common.ImageUtil; + +public class WBMPMetadata extends IIOMetadata { + + static final String nativeMetadataFormatName = + "javax_imageio_wbmp_1.0"; + + public int wbmpType; + + public int width; + public int height; + + public WBMPMetadata() { + super(true, + nativeMetadataFormatName, + "com.sun.imageio.plugins.wbmp.WBMPMetadataFormat", + null, null); + } + + public boolean isReadOnly() { + return true; + } + + public Node getAsTree(String formatName) { + if (formatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } else { + throw new IllegalArgumentException(I18N.getString("WBMPMetadata0")); + } + } + + private Node getNativeTree() { + IIOMetadataNode root = + new IIOMetadataNode(nativeMetadataFormatName); + + addChildNode(root, "WBMPType", new Integer(wbmpType)); + addChildNode(root, "Width", new Integer(width)); + addChildNode(root, "Height", new Integer(height)); + + return root; + } + + public void setFromTree(String formatName, Node root) { + throw new IllegalStateException(I18N.getString("WBMPMetadata1")); + } + + public void mergeTree(String formatName, Node root) { + throw new IllegalStateException(I18N.getString("WBMPMetadata1")); + } + + public void reset() { + throw new IllegalStateException(I18N.getString("WBMPMetadata1")); + } + + private IIOMetadataNode addChildNode(IIOMetadataNode root, + String name, + Object object) { + IIOMetadataNode child = new IIOMetadataNode(name); + if (object != null) { + child.setUserObject(object); + child.setNodeValue(ImageUtil.convertObjectToString(object)); + } + root.appendChild(child); + return child; + } + + + protected IIOMetadataNode getStandardChromaNode() { + + IIOMetadataNode node = new IIOMetadataNode("Chroma"); + IIOMetadataNode subNode = new IIOMetadataNode("BlackIsZero"); + subNode.setAttribute("value", "TRUE"); + + node.appendChild(subNode); + return node; + } + + + protected IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); + IIOMetadataNode node = null; // scratch node + + // PixelAspectRatio not in image + + node = new IIOMetadataNode("ImageOrientation"); + node.setAttribute("value", "Normal"); + dimension_node.appendChild(node); + + return dimension_node; + } + +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPMetadataFormat.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPMetadataFormat.java new file mode 100644 index 000000000..49bce8916 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/plugins/wbmp/WBMPMetadataFormat.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2003, 2004, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.plugins.wbmp; + +import java.util.Arrays; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormat; +import javax.imageio.metadata.IIOMetadataFormatImpl; + +public class WBMPMetadataFormat extends IIOMetadataFormatImpl { + + private static IIOMetadataFormat instance = null; + + private WBMPMetadataFormat() { + super(WBMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_SOME); + + // root -> ImageDescriptor + addElement("ImageDescriptor", + WBMPMetadata.nativeMetadataFormatName, + CHILD_POLICY_EMPTY); + + addAttribute("ImageDescriptor", "WBMPType", + DATATYPE_INTEGER, true, "0"); + + addAttribute("ImageDescriptor", "Width", + DATATYPE_INTEGER, true, null, + "0", "65535", true, true); + addAttribute("ImageDescriptor", "Height", + DATATYPE_INTEGER, true, null, + "1", "65535", true, true); + } + + + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + return true; + } + + public static synchronized IIOMetadataFormat getInstance() { + if (instance == null) { + instance = new WBMPMetadataFormat(); + } + return instance; + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/FileImageInputStreamSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/FileImageInputStreamSpi.java new file mode 100644 index 000000000..5b23588d3 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/FileImageInputStreamSpi.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.spi; + +import java.io.File; +import java.util.Locale; +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.FileImageInputStream; + +public class FileImageInputStreamSpi extends ImageInputStreamSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final Class inputClass = File.class; + + public FileImageInputStreamSpi() { + super(vendorName, version, inputClass); + } + + public String getDescription(Locale locale) { + return "Service provider that instantiates a FileImageInputStream from a File"; + } + + public ImageInputStream createInputStreamInstance(Object input, + boolean useCache, + File cacheDir) { + if (input instanceof File) { + try { + return new FileImageInputStream((File)input); + } catch (Exception e) { + return null; + } + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/FileImageOutputStreamSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/FileImageOutputStreamSpi.java new file mode 100644 index 000000000..61a3a6ed6 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/FileImageOutputStreamSpi.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.spi; + +import java.io.File; +import java.util.Locale; +import javax.imageio.spi.ImageOutputStreamSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.FileImageOutputStream; + +public class FileImageOutputStreamSpi extends ImageOutputStreamSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final Class outputClass = File.class; + + public FileImageOutputStreamSpi() { + super(vendorName, version, outputClass); + } + + public String getDescription(Locale locale) { + return "Service provider that instantiates a FileImageOutputStream from a File"; + } + + public ImageOutputStream createOutputStreamInstance(Object output, + boolean useCache, + File cacheDir) { + if (output instanceof File) { + try { + return new FileImageOutputStream((File)output); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/InputStreamImageInputStreamSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/InputStreamImageInputStreamSpi.java new file mode 100644 index 000000000..5b33133a4 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/InputStreamImageInputStreamSpi.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.spi; + +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.util.Locale; +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.FileCacheImageInputStream; +import javax.imageio.stream.MemoryCacheImageInputStream; + +public class InputStreamImageInputStreamSpi extends ImageInputStreamSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final Class inputClass = InputStream.class; + + public InputStreamImageInputStreamSpi() { + super(vendorName, version, inputClass); + } + + public String getDescription(Locale locale) { + return "Service provider that instantiates a FileCacheImageInputStream or MemoryCacheImageInputStream from an InputStream"; + } + + public boolean canUseCacheFile() { + return true; + } + + public boolean needsCacheFile() { + return false; + } + + public ImageInputStream createInputStreamInstance(Object input, + boolean useCache, + File cacheDir) + throws IOException { + if (input instanceof InputStream) { + InputStream is = (InputStream)input; + + if (useCache) { + return new FileCacheImageInputStream(is, cacheDir); + } else { + return new MemoryCacheImageInputStream(is); + } + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/OutputStreamImageOutputStreamSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/OutputStreamImageOutputStreamSpi.java new file mode 100644 index 000000000..f0e5c856a --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/OutputStreamImageOutputStreamSpi.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.spi; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Locale; +import javax.imageio.spi.ImageOutputStreamSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.FileCacheImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; + +public class OutputStreamImageOutputStreamSpi extends ImageOutputStreamSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final Class outputClass = OutputStream.class; + + public OutputStreamImageOutputStreamSpi() { + super(vendorName, version, outputClass); + } + + public String getDescription(Locale locale) { + return "Service provider that instantiates an OutputStreamImageOutputStream from an OutputStream"; + } + + public boolean canUseCacheFile() { + return true; + } + + public boolean needsCacheFile() { + return false; + } + + public ImageOutputStream createOutputStreamInstance(Object output, + boolean useCache, + File cacheDir) + throws IOException { + if (output instanceof OutputStream) { + OutputStream os = (OutputStream)output; + if (useCache) { + return new FileCacheImageOutputStream(os, cacheDir); + } else { + return new MemoryCacheImageOutputStream(os); + } + } else { + throw new IllegalArgumentException(); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/RAFImageInputStreamSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/RAFImageInputStreamSpi.java new file mode 100644 index 000000000..5ccd59d5e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/RAFImageInputStreamSpi.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.spi; + +import java.io.File; +import java.io.RandomAccessFile; +import java.util.Locale; +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.FileImageInputStream; + +public class RAFImageInputStreamSpi extends ImageInputStreamSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final Class inputClass = RandomAccessFile.class; + + public RAFImageInputStreamSpi() { + super(vendorName, version, inputClass); + } + + public String getDescription(Locale locale) { + return "Service provider that instantiates a FileImageInputStream from a RandomAccessFile"; + } + + public ImageInputStream createInputStreamInstance(Object input, + boolean useCache, + File cacheDir) { + if (input instanceof RandomAccessFile) { + try { + return new FileImageInputStream((RandomAccessFile)input); + } catch (Exception e) { + return null; + } + } else { + throw new IllegalArgumentException + ("input not a RandomAccessFile!"); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/RAFImageOutputStreamSpi.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/RAFImageOutputStreamSpi.java new file mode 100644 index 000000000..9b19f5249 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/spi/RAFImageOutputStreamSpi.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2010, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.spi; + +import java.io.File; +import java.io.RandomAccessFile; +import java.util.Locale; +import javax.imageio.spi.ImageOutputStreamSpi; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.FileImageOutputStream; + +public class RAFImageOutputStreamSpi extends ImageOutputStreamSpi { + + private static final String vendorName = "Oracle Corporation"; + + private static final String version = "1.0"; + + private static final Class outputClass = RandomAccessFile.class; + + public RAFImageOutputStreamSpi() { + super(vendorName, version, outputClass); + } + + public String getDescription(Locale locale) { + return "Service provider that instantiates a FileImageOutputStream from a RandomAccessFile"; + } + + public ImageOutputStream createOutputStreamInstance(Object output, + boolean useCache, + File cacheDir) { + if (output instanceof RandomAccessFile) { + try { + return new FileImageOutputStream((RandomAccessFile)output); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } else { + throw new IllegalArgumentException + ("input not a RandomAccessFile!"); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/CloseableDisposerRecord.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/CloseableDisposerRecord.java new file mode 100644 index 000000000..5ea52def6 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/CloseableDisposerRecord.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.stream; + +import java.io.Closeable; +import java.io.IOException; +import sun.java2d.DisposerRecord; + +/** + * Convenience class that closes a given resource (e.g. RandomAccessFile), + * typically associated with an Image{Input,Output}Stream, prior to the + * stream being garbage collected. + */ +public class CloseableDisposerRecord implements DisposerRecord { + private Closeable closeable; + + public CloseableDisposerRecord(Closeable closeable) { + this.closeable = closeable; + } + + public synchronized void dispose() { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException e) { + } finally { + closeable = null; + } + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/StreamCloser.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/StreamCloser.java new file mode 100644 index 000000000..e4b887d2e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/StreamCloser.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.stream; + +import java.io.IOException; +import java.util.Set; +import java.util.WeakHashMap; +import javax.imageio.stream.ImageInputStream; + +/** + * This class provide means to properly close hanging + * image input/output streams on VM shutdown. + * This might be useful for proper cleanup such as removal + * of temporary files. + * + * Addition of stream do not prevent it from being garbage collected + * if no other references to it exists. Stream can be closed + * explicitly without removal from StreamCloser queue. + * Explicit removal from the queue only helps to save some memory. + */ +public class StreamCloser { + + private static WeakHashMap toCloseQueue; + private static Thread streamCloser; + + public static void addToQueue(CloseAction ca) { + synchronized (StreamCloser.class) { + if (toCloseQueue == null) { + toCloseQueue = + new WeakHashMap(); + } + + toCloseQueue.put(ca, null); + + if (streamCloser == null) { + final Runnable streamCloserRunnable = new Runnable() { + public void run() { + if (toCloseQueue != null) { + synchronized (StreamCloser.class) { + Set set = + toCloseQueue.keySet(); + // Make a copy of the set in order to avoid + // concurrent modification (the is.close() + // will in turn call removeFromQueue()) + CloseAction[] actions = + new CloseAction[set.size()]; + actions = set.toArray(actions); + for (CloseAction ca : actions) { + if (ca != null) { + try { + ca.performAction(); + } catch (IOException e) { + } + } + } + } + } + } + }; + + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + /* The thread must be a member of a thread group + * which will not get GCed before VM exit. + * Make its parent the top-level thread group. + */ + ThreadGroup tg = + Thread.currentThread().getThreadGroup(); + for (ThreadGroup tgn = tg; + tgn != null; + tg = tgn, tgn = tg.getParent()); + streamCloser = new Thread(tg, streamCloserRunnable); + /* Set context class loader to null in order to avoid + * keeping a strong reference to an application classloader. + */ + streamCloser.setContextClassLoader(null); + Runtime.getRuntime().addShutdownHook(streamCloser); + return null; + } + }); + } + } + } + + public static void removeFromQueue(CloseAction ca) { + synchronized (StreamCloser.class) { + if (toCloseQueue != null) { + toCloseQueue.remove(ca); + } + } + } + + public static CloseAction createCloseAction(ImageInputStream iis) { + return new CloseAction(iis); + } + + public static final class CloseAction { + private ImageInputStream iis; + + private CloseAction(ImageInputStream iis) { + this.iis = iis; + } + + public void performAction() throws IOException { + if (iis != null) { + iis.close(); + } + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/StreamFinalizer.java b/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/StreamFinalizer.java new file mode 100644 index 000000000..36954988f --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/com/sun/imageio/stream/StreamFinalizer.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.imageio.stream; + +import java.io.IOException; +import javax.imageio.stream.ImageInputStream; + +/** + * Small class to assist in properly closing an ImageInputStream instance + * prior to garbage collection. The ImageInputStreamImpl class defines a + * finalize() method, but in a number of its public subclasses + * (e.g. FileImageInputStream) we override the finalize() method to be + * empty for performance reasons, and instead rely on the Disposer mechanism + * for closing/disposing resources. This is fine when one of these classes + * is instantiated directly (e.g. new FileImageInputStream()) but in the + * unlikely case where a user defines their own subclass of one of those + * streams, we need some way to get back to the behavior of + * ImageInputStreamImpl, which will call close() as part of finalization. + * + * Typically an Image{Input,Output}Stream will construct an instance of + * StreamFinalizer in its constructor if it detects that it has been + * subclassed by the user. The ImageInputStream instance will hold a + * reference to the StreamFinalizer, and the StreamFinalizer will hold a + * reference back to the ImageInputStream from which it was created. When + * both are no longer reachable, the StreamFinalizer.finalize() method will + * be called, which will take care of closing down the ImageInputStream. + * + * Clearly this is a bit of a hack, but it will likely only be used in the + * rarest of circumstances: when a user has subclassed one of the public + * stream classes. (It should be no worse than the old days when the public + * stream classes had non-empty finalize() methods.) + */ +public class StreamFinalizer { + private ImageInputStream stream; + + public StreamFinalizer(ImageInputStream stream) { + this.stream = stream; + } + + protected void finalize() throws Throwable { + try { + stream.close(); + } catch (IOException e) { + } finally { + stream = null; + super.finalize(); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/awt/image/BufferedImage.java b/sources/net.sf.j2s.java.core/src/java/awt/image/BufferedImage.java index 91348f45f..90a20bd81 100644 --- a/sources/net.sf.j2s.java.core/src/java/awt/image/BufferedImage.java +++ b/sources/net.sf.j2s.java.core/src/java/awt/image/BufferedImage.java @@ -507,76 +507,77 @@ public BufferedImage(int width, int height, int imageType) { this.imageType = imageType; } - // /** - // * Constructs a BufferedImage of one of the predefined - // * image types: - // * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED. - // * - // *

If the image type is TYPE_BYTE_BINARY, the number of - // * entries in the color model is used to determine whether the - // * image should have 1, 2, or 4 bits per pixel. If the color model - // * has 1 or 2 entries, the image will have 1 bit per pixel. If it - // * has 3 or 4 entries, the image with have 2 bits per pixel. If - // * it has between 5 and 16 entries, the image will have 4 bits per - // * pixel. Otherwise, an IllegalArgumentException will be thrown. - // * - // * @param width width of the created image - // * @param height height of the created image - // * @param imageType type of the created image - // * @param cm IndexColorModel of the created image - // * @throws IllegalArgumentException if the imageType is not - // * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED or if the imageType is - // * TYPE_BYTE_BINARY and the color map has more than 16 entries. - // * @see #TYPE_BYTE_BINARY - // * @see #TYPE_BYTE_INDEXED - // */ - // public BufferedImage (int width, - // int height, - // int imageType, - // IndexColorModel cm) { - // if (cm.hasAlpha() && cm.isAlphaPremultiplied()) { - // throw new IllegalArgumentException("This image types do not have "+ - // "premultiplied alpha."); - // } - // - // switch(imageType) { - // case TYPE_BYTE_BINARY: - // int bits; // Will be set below - // int mapSize = cm.getMapSize(); - // if (mapSize <= 2) { - // bits = 1; - // } else if (mapSize <= 4) { - // bits = 2; - // } else if (mapSize <= 16) { - // bits = 4; - // } else { - // throw new IllegalArgumentException - // ("Color map for TYPE_BYTE_BINARY " + - // "must have no more than 16 entries"); - // } - // raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, - // width, height, 1, bits, null); - // break; - // - // case TYPE_BYTE_INDEXED: - // raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, - // width, height, 1, null); - // break; - // default: - // throw new IllegalArgumentException("Invalid image type (" + - // imageType+"). Image type must"+ - // " be either TYPE_BYTE_BINARY or "+ - // " TYPE_BYTE_INDEXED"); - // } - // - // if (!cm.isCompatibleRaster(raster)) { - // throw new - // IllegalArgumentException("Incompatible image type and IndexColorModel"); - // } - // - // colorModel = cm; - // this.imageType = imageType; - // } + /** + * Constructs a BufferedImage of one of the predefined + * image types: + * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED. + * + *

If the image type is TYPE_BYTE_BINARY, the number of + * entries in the color model is used to determine whether the + * image should have 1, 2, or 4 bits per pixel. If the color model + * has 1 or 2 entries, the image will have 1 bit per pixel. If it + * has 3 or 4 entries, the image with have 2 bits per pixel. If + * it has between 5 and 16 entries, the image will have 4 bits per + * pixel. Otherwise, an IllegalArgumentException will be thrown. + * + * @param width width of the created image + * @param height height of the created image + * @param imageType type of the created image + * @param cm IndexColorModel of the created image + * @throws IllegalArgumentException if the imageType is not + * TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED or if the imageType is + * TYPE_BYTE_BINARY and the color map has more than 16 entries. + * @see #TYPE_BYTE_BINARY + * @see #TYPE_BYTE_INDEXED + */ + public BufferedImage (int width, + int height, + int imageType, + IndexColorModel cm) { + if (cm.hasAlpha() && cm.isAlphaPremultiplied()) { + throw new IllegalArgumentException("This image types do not have "+ + "premultiplied alpha."); + } + this.width = width; + this.height = height; + switch(imageType) { + case TYPE_BYTE_BINARY: + int bits; // Will be set below + int mapSize = cm.getMapSize(); + if (mapSize <= 2) { + bits = 1; + } else if (mapSize <= 4) { + bits = 2; + } else if (mapSize <= 16) { + bits = 4; + } else { + throw new IllegalArgumentException + ("Color map for TYPE_BYTE_BINARY " + + "must have no more than 16 entries"); + } + raster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, + width, height, 1, bits, null); + break; + + case TYPE_BYTE_INDEXED: + raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, + width, height, 1, null); + break; + default: + throw new IllegalArgumentException("Invalid image type (" + + imageType+"). Image type must"+ + " be either TYPE_BYTE_BINARY or "+ + " TYPE_BYTE_INDEXED"); + } + + if (!cm.isCompatibleRaster(raster)) { + throw new + IllegalArgumentException("Incompatible image type and IndexColorModel"); + } + + colorModel = cm; + this.imageType = imageType; + } /** * Constructs a new BufferedImage with a specified @@ -635,6 +636,8 @@ public BufferedImage(ColorModel cm, WritableRaster raster, colorModel = cm; this.raster = raster; + this.width = raster.getWidth(); + this.height = raster.getHeight(); raster.setImage(this); 秘pix = ((DataBufferInt) raster.getDataBuffer()).data; this.properties = properties; @@ -845,30 +848,30 @@ public WritableRaster getRaster() { return raster; } - // /** - // * Returns a WritableRaster representing the alpha - // * channel for BufferedImage objects - // * with ColorModel objects that support a separate - // * spatial alpha channel, such as ComponentColorModel and - // * DirectColorModel. Returns null if there - // * is no alpha channel associated with the ColorModel in - // * this image. This method assumes that for all - // * ColorModel objects other than - // * IndexColorModel, if the ColorModel - // * supports alpha, there is a separate alpha channel - // * which is stored as the last band of image data. - // * If the image uses an IndexColorModel that - // * has alpha in the lookup table, this method returns - // * null since there is no spatially discrete alpha - // * channel. This method creates a new - // * WritableRaster, but shares the data array. - // * @return a WritableRaster or null if this - // * BufferedImage has no alpha channel associated - // * with its ColorModel. - // */ - // public WritableRaster getAlphaRaster() { - // return colorModel.getAlphaRaster(raster); - // } + /** + * Returns a WritableRaster representing the alpha + * channel for BufferedImage objects + * with ColorModel objects that support a separate + * spatial alpha channel, such as ComponentColorModel and + * DirectColorModel. Returns null if there + * is no alpha channel associated with the ColorModel in + * this image. This method assumes that for all + * ColorModel objects other than + * IndexColorModel, if the ColorModel + * supports alpha, there is a separate alpha channel + * which is stored as the last band of image data. + * If the image uses an IndexColorModel that + * has alpha in the lookup table, this method returns + * null since there is no spatially discrete alpha + * channel. This method creates a new + * WritableRaster, but shares the data array. + * @return a WritableRaster or null if this + * BufferedImage has no alpha channel associated + * with its ColorModel. + */ + public WritableRaster getAlphaRaster() { + return colorModel.getAlphaRaster(raster); + } @@ -1601,61 +1604,61 @@ public void setData(Raster r) { // throw new IllegalArgumentException("Only 1 tile in image"); // } // - // /** - // * Returns an array of {@link Point} objects indicating which tiles - // * are checked out for writing. Returns null if none are - // * checked out. - // * @return a Point array that indicates the tiles that - // * are checked out for writing, or null if no - // * tiles are checked out for writing. - // */ - // public Point[] getWritableTileIndices() { - // Point[] p = new Point[1]; - // p[0] = new Point(0, 0); - // - // return p; - // } - // - // /** - // * Returns whether or not any tile is checked out for writing. - // * Semantically equivalent to - // *

-	// * (getWritableTileIndices() != null).
-	// * 
- // * @return true if any tile is checked out for writing; - // * false otherwise. - // */ - // public boolean hasTileWriters () { - // return true; - // } - // - // // /** - // // * Checks out a tile for writing. All registered - // // * TileObservers are notified when a tile goes from having - // // * no writers to having one writer. - // // * @param tileX the x index of the tile - // // * @param tileY the y index of the tile - // // * @return a WritableRaster that is the tile, indicated by - // // * the specified indices, to be checked out for writing. - // // */ - // // public WritableRaster getWritableTile (int tileX, int tileY) { - // // return raster; - // // } - // - // /** - // * Relinquishes permission to write to a tile. If the caller - // * continues to write to the tile, the results are undefined. - // * Calls to this method should only appear in matching pairs - // * with calls to {@link #getWritableTile(int, int) getWritableTile(int, - // int)}. Any other leads - // * to undefined results. All registered TileObservers - // * are notified when a tile goes from having one writer to having no - // * writers. - // * @param tileX the x index of the tile - // * @param tileY the y index of the tile - // */ - // public void releaseWritableTile (int tileX, int tileY) { - // } + /** + * Returns an array of {@link Point} objects indicating which tiles + * are checked out for writing. Returns null if none are + * checked out. + * @return a Point array that indicates the tiles that + * are checked out for writing, or null if no + * tiles are checked out for writing. + */ + public Point[] getWritableTileIndices() { + Point[] p = new Point[1]; + p[0] = new Point(0, 0); + + return p; + } + + /** + * Returns whether or not any tile is checked out for writing. + * Semantically equivalent to + *
+	 * (getWritableTileIndices() != null).
+	 * 
+ * @return true if any tile is checked out for writing; + * false otherwise. + */ + public boolean hasTileWriters () { + return true; + } + + /** + * Checks out a tile for writing. All registered + * TileObservers are notified when a tile goes from having + * no writers to having one writer. + * @param tileX the x index of the tile + * @param tileY the y index of the tile + * @return a WritableRaster that is the tile, indicated by + * the specified indices, to be checked out for writing. + */ + public WritableRaster getWritableTile (int tileX, int tileY) { + return raster; + } + + /** + * Relinquishes permission to write to a tile. If the caller + * continues to write to the tile, the results are undefined. + * Calls to this method should only appear in matching pairs + * with calls to {@link #getWritableTile(int, int) getWritableTile(int, + int)}. Any other leads + * to undefined results. All registered TileObservers + * are notified when a tile goes from having one writer to having no + * writers. + * @param tileX the x index of the tile + * @param tileY the y index of the tile + */ + public void releaseWritableTile (int tileX, int tileY) { + } /** * Returns the transparency. Returns either OPAQUE, BITMASK, or TRANSLUCENT. diff --git a/sources/net.sf.j2s.java.core/src/java/awt/image/ColorModel.java b/sources/net.sf.j2s.java.core/src/java/awt/image/ColorModel.java index 90b7367cb..e52641a5f 100644 --- a/sources/net.sf.j2s.java.core/src/java/awt/image/ColorModel.java +++ b/sources/net.sf.j2s.java.core/src/java/awt/image/ColorModel.java @@ -1480,33 +1480,33 @@ final public ColorSpace getColorSpace() { return colorSpace; } -// /** -// * Forces the raster data to match the state specified in the -// * isAlphaPremultiplied variable, assuming the data is -// * currently correctly described by this ColorModel. It -// * may multiply or divide the color raster data by alpha, or do -// * nothing if the data is in the correct state. If the data needs to -// * be coerced, this method will also return an instance of this -// * ColorModel with the isAlphaPremultiplied -// * flag set appropriately. This method will throw a -// * UnsupportedOperationException if it is not supported -// * by this ColorModel. -// * Since ColorModel is an abstract class, -// * any instance is an instance of a subclass. Subclasses must -// * override this method since the implementation in this abstract -// * class throws an UnsupportedOperationException. -// * @param raster the WritableRaster data -// * @param isAlphaPremultiplied true if the alpha is -// * premultiplied; false otherwise -// * @return a ColorModel object that represents the -// * coerced data. -// */ -// public ColorModel coerceData (WritableRaster raster, -// boolean isAlphaPremultiplied) { -// throw new UnsupportedOperationException -// ("This method is not supported by this color model"); -// } -// + /** + * Forces the raster data to match the state specified in the + * isAlphaPremultiplied variable, assuming the data is + * currently correctly described by this ColorModel. It + * may multiply or divide the color raster data by alpha, or do + * nothing if the data is in the correct state. If the data needs to + * be coerced, this method will also return an instance of this + * ColorModel with the isAlphaPremultiplied + * flag set appropriately. This method will throw a + * UnsupportedOperationException if it is not supported + * by this ColorModel. + * Since ColorModel is an abstract class, + * any instance is an instance of a subclass. Subclasses must + * override this method since the implementation in this abstract + * class throws an UnsupportedOperationException. + * @param raster the WritableRaster data + * @param isAlphaPremultiplied true if the alpha is + * premultiplied; false otherwise + * @return a ColorModel object that represents the + * coerced data. + */ + public ColorModel coerceData (WritableRaster raster, + boolean isAlphaPremultiplied) { + throw new UnsupportedOperationException + ("This method is not supported by this color model"); + } + /** * Returns true if raster is compatible * with this ColorModel and false if it is diff --git a/sources/net.sf.j2s.java.core/src/java/awt/image/ComponentColorModel.java b/sources/net.sf.j2s.java.core/src/java/awt/image/ComponentColorModel.java index 5711103d2..98a039bc0 100644 --- a/sources/net.sf.j2s.java.core/src/java/awt/image/ComponentColorModel.java +++ b/sources/net.sf.j2s.java.core/src/java/awt/image/ComponentColorModel.java @@ -669,7 +669,8 @@ private int getRGBComponent(int pixel, int idx) { * @throws IllegalArgumentException If the component value for this * ColorModel is signed */ - public int getRed(int pixel) { + @Override + public int getRed(int pixel) { return getRGBComponent(pixel, 0); } @@ -691,7 +692,8 @@ public int getRed(int pixel) { * @throws IllegalArgumentException If the component value for this * ColorModel is signed */ - public int getGreen(int pixel) { + @Override + public int getGreen(int pixel) { return getRGBComponent(pixel, 1); } @@ -713,7 +715,8 @@ public int getGreen(int pixel) { * @throws IllegalArgumentException If the component value for this * ColorModel is signed */ - public int getBlue(int pixel) { + @Override + public int getBlue(int pixel) { return getRGBComponent(pixel, 2); } @@ -730,7 +733,8 @@ public int getBlue(int pixel) { * @throws IllegalArgumentException If the component value for this * ColorModel is signed */ - public int getAlpha(int pixel) { + @Override + public int getAlpha(int pixel) { if (supportsAlpha == false) { return 255; } @@ -762,7 +766,8 @@ public int getAlpha(int pixel) { * @throws IllegalArgumentException If the component value for this * ColorModel is signed */ - public int getRGB(int pixel) { + @Override + public int getRGB(int pixel) { if (numComponents > 1) { throw new IllegalArgumentException("More than one component per pixel"); @@ -943,7 +948,8 @@ private int getRGBComponent(Object inData, int idx) { * DataBuffer.TYPE_INT, DataBuffer.TYPE_SHORT, * DataBuffer.TYPE_FLOAT, or DataBuffer.TYPE_DOUBLE. */ - public int getRed(Object inData) { + @Override + public int getRed(Object inData) { return getRGBComponent(inData, 0); } @@ -978,7 +984,8 @@ public int getRed(Object inData) { * DataBuffer.TYPE_INT, DataBuffer.TYPE_SHORT, * DataBuffer.TYPE_FLOAT, or DataBuffer.TYPE_DOUBLE. */ - public int getGreen(Object inData) { + @Override + public int getGreen(Object inData) { return getRGBComponent(inData, 1); } @@ -1013,7 +1020,8 @@ public int getGreen(Object inData) { * DataBuffer.TYPE_INT, DataBuffer.TYPE_SHORT, * DataBuffer.TYPE_FLOAT, or DataBuffer.TYPE_DOUBLE. */ - public int getBlue(Object inData) { + @Override + public int getBlue(Object inData) { return getRGBComponent(inData, 2); } @@ -1044,7 +1052,8 @@ public int getBlue(Object inData) { * DataBuffer.TYPE_INT, DataBuffer.TYPE_SHORT, * DataBuffer.TYPE_FLOAT, or DataBuffer.TYPE_DOUBLE. */ - public int getAlpha(Object inData) { + @Override + public int getAlpha(Object inData) { if (supportsAlpha == false) { return 255; } @@ -1125,7 +1134,8 @@ public int getAlpha(Object inData) { * DataBuffer.TYPE_FLOAT, or DataBuffer.TYPE_DOUBLE. * @see ColorModel#getRGBdefault */ - public int getRGB(Object inData) { + @Override + public int getRGB(Object inData) { if (needScaleInit) { initScale(); } @@ -1185,7 +1195,8 @@ public int getRGB(Object inData) { * @see WritableRaster#setDataElements * @see SampleModel#setDataElements */ - public Object getDataElements(int rgb, Object pixel) { + @Override + public Object getDataElements(int rgb, Object pixel) { // REMIND: Use rendering hints? int red, grn, blu, alp; @@ -1681,7 +1692,8 @@ public Object getDataElements(int rgb, Object pixel) { * array is not null and is not large enough to hold all the color and * alpha components (starting at offset). */ - public int[] getComponents(int pixel, int[] components, int offset) { + @Override + public int[] getComponents(int pixel, int[] components, int offset) { if (numComponents > 1) { throw new IllegalArgumentException("More than one component per pixel"); @@ -1741,7 +1753,8 @@ public int[] getComponents(int pixel, int[] components, int offset) { * components (starting at offset), or if pixel is not large * enough to hold a pixel value for this ColorModel. */ - public int[] getComponents(Object pixel, int[] components, int offset) { + @Override + public int[] getComponents(Object pixel, int[] components, int offset) { int intpixel[]; if (needScaleInit) { initScale(); @@ -1816,7 +1829,8 @@ else if ((components.length-offset) < numComponents) { * normComponents minus normOffset * is less than numComponents */ - public int[] getUnnormalizedComponents(float[] normComponents, + @Override + public int[] getUnnormalizedComponents(float[] normComponents, int normOffset, int[] components, int offset) { if (needScaleInit) { @@ -1865,7 +1879,8 @@ public int[] getUnnormalizedComponents(float[] normComponents, * @throws IllegalArgumentException If this * ComponentColorModel does not support the unnormalized form */ - public float[] getNormalizedComponents(int[] components, int offset, + @Override + public float[] getNormalizedComponents(int[] components, int offset, float[] normComponents, int normOffset) { if (needScaleInit) { @@ -1894,7 +1909,8 @@ public float[] getNormalizedComponents(int[] components, int offset, * @throws IllegalArgumentException If this * ComponentColorModel does not support the unnormalized form */ - public int getDataElement(int[] components, int offset) { + @Override + public int getDataElement(int[] components, int offset) { if (needScaleInit) { initScale(); } @@ -1952,7 +1968,8 @@ public int getDataElement(int[] components, int offset) { * @see WritableRaster#setDataElements * @see SampleModel#setDataElements */ - public Object getDataElements(int[] components, int offset, Object obj) { + @Override + public Object getDataElements(int[] components, int offset, Object obj) { if (needScaleInit) { initScale(); } @@ -2042,7 +2059,8 @@ public Object getDataElements(int[] components, int offset, Object obj) { * normOffset * @since 1.4 */ - public int getDataElement(float[] normComponents, int normOffset) { + @Override + public int getDataElement(float[] normComponents, int normOffset) { if (numComponents > 1) { throw new IllegalArgumentException("More than one component per pixel"); @@ -2111,7 +2129,8 @@ public int getDataElement(float[] normComponents, int normOffset) { * @see SampleModel#setDataElements * @since 1.4 */ - public Object getDataElements(float[] normComponents, int normOffset, + @Override + public Object getDataElements(float[] normComponents, int normOffset, Object obj) { boolean needAlpha = supportsAlpha && isAlphaPremultiplied; float[] stdNormComponents; @@ -2343,7 +2362,8 @@ public Object getDataElements(float[] normComponents, int normOffset, * value for this ColorModel. * @since 1.4 */ - public float[] getNormalizedComponents(Object pixel, + @Override + public float[] getNormalizedComponents(Object pixel, float[] normComponents, int normOffset) { if (normComponents == null) { @@ -2450,7 +2470,8 @@ public float[] getNormalizedComponents(Object pixel, * DataBuffer.TYPE_INT, DataBuffer.TYPE_SHORT, * DataBuffer.TYPE_FLOAT, or DataBuffer.TYPE_DOUBLE. */ - public ColorModel coerceData (WritableRaster raster, + @Override + public ColorModel coerceData (WritableRaster raster, boolean isAlphaPremultiplied) { if ((supportsAlpha == false) || (this.isAlphaPremultiplied == isAlphaPremultiplied)) @@ -2785,7 +2806,8 @@ public ColorModel coerceData (WritableRaster raster, * @return true if raster is compatible with this * ColorModel, false if it is not. */ - public boolean isCompatibleRaster(Raster raster) { + @Override + public boolean isCompatibleRaster(Raster raster) { SampleModel sm = raster.getSampleModel(); @@ -2818,7 +2840,8 @@ public boolean isCompatibleRaster(Raster raster) { * @see WritableRaster * @see SampleModel */ - public WritableRaster createCompatibleWritableRaster (int w, int h) { + @Override + public WritableRaster createCompatibleWritableRaster (int w, int h) { // int dataSize = w*h*numComponents; WritableRaster raster = null; @@ -2850,7 +2873,8 @@ public WritableRaster createCompatibleWritableRaster (int w, int h) { * * @see SampleModel */ - public SampleModel createCompatibleSampleModel(int w, int h) { + @Override + public SampleModel createCompatibleSampleModel(int w, int h) { int[] bandOffsets = new int[numComponents]; for (int i=0; i < numComponents; i++) { bandOffsets[i] = i; @@ -2883,7 +2907,8 @@ public SampleModel createCompatibleSampleModel(int w, int h) { * * @see SampleModel */ - public boolean isCompatibleSampleModel(SampleModel sm) { + @Override + public boolean isCompatibleSampleModel(SampleModel sm) { if (!(sm instanceof ComponentSampleModel)) { return false; } @@ -2916,7 +2941,8 @@ public boolean isCompatibleSampleModel(SampleModel sm) { * @return A WritableRaster containing the image's alpha channel. * */ - public WritableRaster getAlphaRaster(WritableRaster raster) { + @Override + public WritableRaster getAlphaRaster(WritableRaster raster) { if (hasAlpha() == false) { return null; } @@ -2937,7 +2963,8 @@ public WritableRaster getAlphaRaster(WritableRaster raster) { * @return true if the color model objects are equal, * false if they are not. */ - public boolean equals(Object obj) { + @Override + public boolean equals(Object obj) { if (!super.equals(obj)) { return false; } diff --git a/sources/net.sf.j2s.java.core/src/java/awt/image/DataBufferUShort.java b/sources/net.sf.j2s.java.core/src/java/awt/image/DataBufferUShort.java index 8ed85929c..057d68026 100644 --- a/sources/net.sf.j2s.java.core/src/java/awt/image/DataBufferUShort.java +++ b/sources/net.sf.j2s.java.core/src/java/awt/image/DataBufferUShort.java @@ -67,7 +67,7 @@ public final class DataBufferUShort extends DataBuffer short data[]; /** All data banks */ - short bankdata[][]; + public short bankdata[][]; /** * Constructs an unsigned-short based DataBuffer with a single bank and the diff --git a/sources/net.sf.j2s.java.core/src/java/awt/image/Raster.java b/sources/net.sf.j2s.java.core/src/java/awt/image/Raster.java index 450fb7ddb..4ae4868df 100644 --- a/sources/net.sf.j2s.java.core/src/java/awt/image/Raster.java +++ b/sources/net.sf.j2s.java.core/src/java/awt/image/Raster.java @@ -38,11 +38,17 @@ package java.awt.image; +import swingjs.JSUtil; import swingjs.api.Interface; import java.awt.Rectangle; import java.awt.Point; +import sun.awt.image.ByteBandedRaster; +import sun.awt.image.ByteInterleavedRaster; import sun.awt.image.BytePackedRaster; +import sun.awt.image.IntegerInterleavedRaster; +import sun.awt.image.ShortBandedRaster; +import sun.awt.image.ShortInterleavedRaster; import sun.awt.image.SunWritableRaster; /** @@ -179,249 +185,249 @@ public void setImage(BufferedImage image) { // initIDs(); // } // -// /** -// * Creates a Raster based on a PixelInterleavedSampleModel with the -// * specified data type, width, height, and number of bands. -// * -// *

The upper left corner of the Raster is given by the -// * location argument. If location is null, (0, 0) will be used. -// * The dataType parameter should be one of the enumerated values -// * defined in the DataBuffer class. -// * -// *

Note that interleaved DataBuffer.TYPE_INT -// * Rasters are not supported. To create a 1-band Raster of type -// * DataBuffer.TYPE_INT, use -// * Raster.createPackedRaster(). -// *

The only dataTypes supported currently are TYPE_BYTE -// * and TYPE_USHORT. -// * @param dataType the data type for storing samples -// * @param w the width in pixels of the image data -// * @param h the height in pixels of the image data -// * @param bands the number of bands -// * @param location the upper-left corner of the Raster -// * @return a WritableRaster object with the specified data type, -// * width, height and number of bands. -// * @throws RasterFormatException if w or h -// * is less than or equal to zero, or computing either -// * location.x + w or -// * location.y + h results in integer -// * overflow -// */ -// public static WritableRaster createInterleavedRaster(int dataType, -// int w, int h, -// int bands, -// Point location) { -// int[] bandOffsets = new int[bands]; -// for (int i = 0; i < bands; i++) { -// bandOffsets[i] = i; -// } -// return createInterleavedRaster(dataType, w, h, w*bands, bands, -// bandOffsets, location); -// } -// -// /** -// * Creates a Raster based on a PixelInterleavedSampleModel with the -// * specified data type, width, height, scanline stride, pixel -// * stride, and band offsets. The number of bands is inferred from -// * bandOffsets.length. -// * -// *

The upper left corner of the Raster is given by the -// * location argument. If location is null, (0, 0) will be used. -// * The dataType parameter should be one of the enumerated values -// * defined in the DataBuffer class. -// * -// *

Note that interleaved DataBuffer.TYPE_INT -// * Rasters are not supported. To create a 1-band Raster of type -// * DataBuffer.TYPE_INT, use -// * Raster.createPackedRaster(). -// *

The only dataTypes supported currently are TYPE_BYTE -// * and TYPE_USHORT. -// * @param dataType the data type for storing samples -// * @param w the width in pixels of the image data -// * @param h the height in pixels of the image data -// * @param scanlineStride the line stride of the image data -// * @param pixelStride the pixel stride of the image data -// * @param bandOffsets the offsets of all bands -// * @param location the upper-left corner of the Raster -// * @return a WritableRaster object with the specified data type, -// * width, height, scanline stride, pixel stride and band -// * offsets. -// * @throws RasterFormatException if w or h -// * is less than or equal to zero, or computing either -// * location.x + w or -// * location.y + h results in integer -// * overflow -// * @throws IllegalArgumentException if dataType is not -// * one of the supported data types, which are -// * DataBuffer.TYPE_BYTE, or -// * DataBuffer.TYPE_USHORT. -// */ -// public static WritableRaster createInterleavedRaster(int dataType, -// int w, int h, -// int scanlineStride, -// int pixelStride, -// int bandOffsets[], -// Point location) { -// DataBuffer d; -// -// int size = scanlineStride * (h - 1) + // fisrt (h - 1) scans -// pixelStride * w; // last scan -// -// switch(dataType) { -// case DataBuffer.TYPE_BYTE: -// d = new DataBufferByte(size); -// break; -// -//// case DataBuffer.TYPE_USHORT: -//// d = new DataBufferUShort(size); -//// break; -//// -// default: -// throw new IllegalArgumentException("Unsupported data type " + -// dataType); -// } -// -// return createInterleavedRaster(d, w, h, scanlineStride, -// pixelStride, bandOffsets, location); -// } -// -// /** -// * Creates a Raster based on a BandedSampleModel with the -// * specified data type, width, height, and number of bands. -// * -// *

The upper left corner of the Raster is given by the -// * location argument. If location is null, (0, 0) will be used. -// * The dataType parameter should be one of the enumerated values -// * defined in the DataBuffer class. -// * -// *

The only dataTypes supported currently are TYPE_BYTE, TYPE_USHORT, -// * and TYPE_INT. -// * @param dataType the data type for storing samples -// * @param w the width in pixels of the image data -// * @param h the height in pixels of the image data -// * @param bands the number of bands -// * @param location the upper-left corner of the Raster -// * @return a WritableRaster object with the specified data type, -// * width, height and number of bands. -// * @throws RasterFormatException if w or h -// * is less than or equal to zero, or computing either -// * location.x + w or -// * location.y + h results in integer -// * overflow -// * @throws ArrayIndexOutOfBoundsException if bands -// * is less than 1 -// */ -// public static WritableRaster createBandedRaster(int dataType, -// int w, int h, -// int bands, -// Point location) { -// if (bands < 1) { -// throw new ArrayIndexOutOfBoundsException("Number of bands ("+ -// bands+") must"+ -// " be greater than 0"); -// } -// int[] bankIndices = new int[bands]; -// int[] bandOffsets = new int[bands]; -// for (int i = 0; i < bands; i++) { -// bankIndices[i] = i; -// bandOffsets[i] = 0; -// } -// -// return createBandedRaster(dataType, w, h, w, -// bankIndices, bandOffsets, -// location); -// } -// -// /** -// * Creates a Raster based on a BandedSampleModel with the -// * specified data type, width, height, scanline stride, bank -// * indices and band offsets. The number of bands is inferred from -// * bankIndices.length and bandOffsets.length, which must be the -// * same. -// * -// *

The upper left corner of the Raster is given by the -// * location argument. The dataType parameter should be one of the -// * enumerated values defined in the DataBuffer class. -// * -// *

The only dataTypes supported currently are TYPE_BYTE, TYPE_USHORT, -// * and TYPE_INT. -// * @param dataType the data type for storing samples -// * @param w the width in pixels of the image data -// * @param h the height in pixels of the image data -// * @param scanlineStride the line stride of the image data -// * @param bankIndices the bank indices for each band -// * @param bandOffsets the offsets of all bands -// * @param location the upper-left corner of the Raster -// * @return a WritableRaster object with the specified data type, -// * width, height, scanline stride, bank indices and band -// * offsets. -// * @throws RasterFormatException if w or h -// * is less than or equal to zero, or computing either -// * location.x + w or -// * location.y + h results in integer -// * overflow -// * @throws IllegalArgumentException if dataType is not -// * one of the supported data types, which are -// * DataBuffer.TYPE_BYTE, -// * DataBuffer.TYPE_USHORT -// * or DataBuffer.TYPE_INT -// * @throws ArrayIndexOutOfBoundsException if bankIndices -// * or bandOffsets is null -// */ -// public static WritableRaster createBandedRaster(int dataType, -// int w, int h, -// int scanlineStride, -// int bankIndices[], -// int bandOffsets[], -// Point location) { -// DataBuffer d; -// int bands = bandOffsets.length; -// -// if (bankIndices == null) { -// throw new -// ArrayIndexOutOfBoundsException("Bank indices array is null"); -// } -// if (bandOffsets == null) { -// throw new -// ArrayIndexOutOfBoundsException("Band offsets array is null"); -// } -// -// // Figure out the #banks and the largest band offset -// int maxBank = bankIndices[0]; -// int maxBandOff = bandOffsets[0]; -// for (int i = 1; i < bands; i++) { -// if (bankIndices[i] > maxBank) { -// maxBank = bankIndices[i]; -// } -// if (bandOffsets[i] > maxBandOff) { -// maxBandOff = bandOffsets[i]; -// } -// } -// int banks = maxBank + 1; -// int size = scanlineStride * (h - 1) + // fisrt (h - 1) scans -// w; // last scan -// -// switch(dataType) { -// case DataBuffer.TYPE_BYTE: -// d = new DataBufferByte(size, banks); -// break; -// -//// case DataBuffer.TYPE_USHORT: -//// d = new DataBufferUShort(size, banks); -//// break; -//// -// case DataBuffer.TYPE_INT: -// d = new DataBufferInt(size, banks); + /** + * Creates a Raster based on a PixelInterleavedSampleModel with the + * specified data type, width, height, and number of bands. + * + *

The upper left corner of the Raster is given by the + * location argument. If location is null, (0, 0) will be used. + * The dataType parameter should be one of the enumerated values + * defined in the DataBuffer class. + * + *

Note that interleaved DataBuffer.TYPE_INT + * Rasters are not supported. To create a 1-band Raster of type + * DataBuffer.TYPE_INT, use + * Raster.createPackedRaster(). + *

The only dataTypes supported currently are TYPE_BYTE + * and TYPE_USHORT. + * @param dataType the data type for storing samples + * @param w the width in pixels of the image data + * @param h the height in pixels of the image data + * @param bands the number of bands + * @param location the upper-left corner of the Raster + * @return a WritableRaster object with the specified data type, + * width, height and number of bands. + * @throws RasterFormatException if w or h + * is less than or equal to zero, or computing either + * location.x + w or + * location.y + h results in integer + * overflow + */ + public static WritableRaster createInterleavedRaster(int dataType, + int w, int h, + int bands, + Point location) { + int[] bandOffsets = new int[bands]; + for (int i = 0; i < bands; i++) { + bandOffsets[i] = i; + } + return createInterleavedRaster(dataType, w, h, w*bands, bands, + bandOffsets, location); + } + + /** + * Creates a Raster based on a PixelInterleavedSampleModel with the + * specified data type, width, height, scanline stride, pixel + * stride, and band offsets. The number of bands is inferred from + * bandOffsets.length. + * + *

The upper left corner of the Raster is given by the + * location argument. If location is null, (0, 0) will be used. + * The dataType parameter should be one of the enumerated values + * defined in the DataBuffer class. + * + *

Note that interleaved DataBuffer.TYPE_INT + * Rasters are not supported. To create a 1-band Raster of type + * DataBuffer.TYPE_INT, use + * Raster.createPackedRaster(). + *

The only dataTypes supported currently are TYPE_BYTE + * and TYPE_USHORT. + * @param dataType the data type for storing samples + * @param w the width in pixels of the image data + * @param h the height in pixels of the image data + * @param scanlineStride the line stride of the image data + * @param pixelStride the pixel stride of the image data + * @param bandOffsets the offsets of all bands + * @param location the upper-left corner of the Raster + * @return a WritableRaster object with the specified data type, + * width, height, scanline stride, pixel stride and band + * offsets. + * @throws RasterFormatException if w or h + * is less than or equal to zero, or computing either + * location.x + w or + * location.y + h results in integer + * overflow + * @throws IllegalArgumentException if dataType is not + * one of the supported data types, which are + * DataBuffer.TYPE_BYTE, or + * DataBuffer.TYPE_USHORT. + */ + public static WritableRaster createInterleavedRaster(int dataType, + int w, int h, + int scanlineStride, + int pixelStride, + int bandOffsets[], + Point location) { + DataBuffer d; + + int size = scanlineStride * (h - 1) + // fisrt (h - 1) scans + pixelStride * w; // last scan + + switch(dataType) { + case DataBuffer.TYPE_BYTE: + d = new DataBufferByte(size); + break; + case DataBuffer.TYPE_USHORT: + d = new DataBufferUShort(size); + break; + + default: + throw new IllegalArgumentException("Unsupported data type " + + dataType); + } + + return createInterleavedRaster(d, w, h, scanlineStride, + pixelStride, bandOffsets, location); + } + + /** + * Creates a Raster based on a BandedSampleModel with the + * specified data type, width, height, and number of bands. + * + *

The upper left corner of the Raster is given by the + * location argument. If location is null, (0, 0) will be used. + * The dataType parameter should be one of the enumerated values + * defined in the DataBuffer class. + * + *

The only dataTypes supported currently are TYPE_BYTE, TYPE_USHORT, + * and TYPE_INT. + * @param dataType the data type for storing samples + * @param w the width in pixels of the image data + * @param h the height in pixels of the image data + * @param bands the number of bands + * @param location the upper-left corner of the Raster + * @return a WritableRaster object with the specified data type, + * width, height and number of bands. + * @throws RasterFormatException if w or h + * is less than or equal to zero, or computing either + * location.x + w or + * location.y + h results in integer + * overflow + * @throws ArrayIndexOutOfBoundsException if bands + * is less than 1 + */ + public static WritableRaster createBandedRaster(int dataType, + int w, int h, + int bands, + Point location) { + if (bands < 1) { + throw new ArrayIndexOutOfBoundsException("Number of bands ("+ + bands+") must"+ + " be greater than 0"); + } + int[] bankIndices = new int[bands]; + int[] bandOffsets = new int[bands]; + for (int i = 0; i < bands; i++) { + bankIndices[i] = i; + bandOffsets[i] = 0; + } + + return createBandedRaster(dataType, w, h, w, + bankIndices, bandOffsets, + location); + } + + /** + * Creates a Raster based on a BandedSampleModel with the + * specified data type, width, height, scanline stride, bank + * indices and band offsets. The number of bands is inferred from + * bankIndices.length and bandOffsets.length, which must be the + * same. + * + *

The upper left corner of the Raster is given by the + * location argument. The dataType parameter should be one of the + * enumerated values defined in the DataBuffer class. + * + *

The only dataTypes supported currently are TYPE_BYTE, TYPE_USHORT, + * and TYPE_INT. + * @param dataType the data type for storing samples + * @param w the width in pixels of the image data + * @param h the height in pixels of the image data + * @param scanlineStride the line stride of the image data + * @param bankIndices the bank indices for each band + * @param bandOffsets the offsets of all bands + * @param location the upper-left corner of the Raster + * @return a WritableRaster object with the specified data type, + * width, height, scanline stride, bank indices and band + * offsets. + * @throws RasterFormatException if w or h + * is less than or equal to zero, or computing either + * location.x + w or + * location.y + h results in integer + * overflow + * @throws IllegalArgumentException if dataType is not + * one of the supported data types, which are + * DataBuffer.TYPE_BYTE, + * DataBuffer.TYPE_USHORT + * or DataBuffer.TYPE_INT + * @throws ArrayIndexOutOfBoundsException if bankIndices + * or bandOffsets is null + */ + public static WritableRaster createBandedRaster(int dataType, + int w, int h, + int scanlineStride, + int bankIndices[], + int bandOffsets[], + Point location) { + DataBuffer d; + + if (bankIndices == null) { + throw new + ArrayIndexOutOfBoundsException("Bank indices array is null"); + } + if (bandOffsets == null) { + throw new + ArrayIndexOutOfBoundsException("Band offsets array is null"); + } + + int bands = bandOffsets.length; + + // Figure out the #banks and the largest band offset + int maxBank = bankIndices[0]; + int maxBandOff = bandOffsets[0]; + for (int i = 1; i < bands; i++) { + if (bankIndices[i] > maxBank) { + maxBank = bankIndices[i]; + } + if (bandOffsets[i] > maxBandOff) { + maxBandOff = bandOffsets[i]; + } + } + int banks = maxBank + 1; + int size = scanlineStride * (h - 1) + // fisrt (h - 1) scans + w; // last scan + + switch(dataType) { + case DataBuffer.TYPE_BYTE: + d = new DataBufferByte(size, banks); + break; + +// case DataBuffer.TYPE_USHORT: +// d = new DataBufferUShort(size, banks); // break; // -// default: -// throw new IllegalArgumentException("Unsupported data type " + -// dataType); -// } -// -// return createBandedRaster(d, w, h, scanlineStride, -// bankIndices, bandOffsets, location); -// } + case DataBuffer.TYPE_INT: + d = new DataBufferInt(size, banks); + break; + + default: + throw new IllegalArgumentException("Unsupported data type " + + dataType); + } + + return createBandedRaster(d, w, h, scanlineStride, + bankIndices, bandOffsets, location); + } /** * Creates a Raster based on a SinglePixelPackedSampleModel with @@ -594,144 +600,145 @@ public static WritableRaster createPackedRaster(int dataType, } } -// /** -// * Creates a Raster based on a PixelInterleavedSampleModel with the -// * specified DataBuffer, width, height, scanline stride, pixel -// * stride, and band offsets. The number of bands is inferred from -// * bandOffsets.length. The upper left corner of the Raster -// * is given by the location argument. If location is null, (0, 0) -// * will be used. -// *

Note that interleaved DataBuffer.TYPE_INT -// * Rasters are not supported. To create a 1-band Raster of type -// * DataBuffer.TYPE_INT, use -// * Raster.createPackedRaster(). -// * @param dataBuffer the DataBuffer that contains the -// * image data -// * @param w the width in pixels of the image data -// * @param h the height in pixels of the image data -// * @param scanlineStride the line stride of the image data -// * @param pixelStride the pixel stride of the image data -// * @param bandOffsets the offsets of all bands -// * @param location the upper-left corner of the Raster -// * @return a WritableRaster object with the specified -// * DataBuffer, width, height, scanline stride, -// * pixel stride and band offsets. -// * @throws RasterFormatException if w or h -// * is less than or equal to zero, or computing either -// * location.x + w or -// * location.y + h results in integer -// * overflow -// * @throws IllegalArgumentException if dataType is not -// * one of the supported data types, which are -// * DataBuffer.TYPE_BYTE, -// * DataBuffer.TYPE_USHORT -// * @throws RasterFormatException if dataBuffer has more -// * than one bank. -// * @throws NullPointerException if dataBuffer is null -// */ -// public static WritableRaster createInterleavedRaster(DataBuffer dataBuffer, -// int w, int h, -// int scanlineStride, -// int pixelStride, -// int bandOffsets[], -// Point location) { -// if (dataBuffer == null) { -// throw new NullPointerException("DataBuffer cannot be null"); -// } -// if (location == null) { -// location = new Point(0, 0); -// } -// int dataType = dataBuffer.getDataType(); -// -// PixelInterleavedSampleModel csm = -// new PixelInterleavedSampleModel(dataType, w, h, -// pixelStride, -// scanlineStride, -// bandOffsets); -// switch(dataType) { -// case DataBuffer.TYPE_BYTE: -// return new ByteInterleavedRaster(csm, dataBuffer, location); -// -// case DataBuffer.TYPE_USHORT: + /** + * Creates a Raster based on a PixelInterleavedSampleModel with the + * specified DataBuffer, width, height, scanline stride, pixel + * stride, and band offsets. The number of bands is inferred from + * bandOffsets.length. The upper left corner of the Raster + * is given by the location argument. If location is null, (0, 0) + * will be used. + *

Note that interleaved DataBuffer.TYPE_INT + * Rasters are not supported. To create a 1-band Raster of type + * DataBuffer.TYPE_INT, use + * Raster.createPackedRaster(). + * @param dataBuffer the DataBuffer that contains the + * image data + * @param w the width in pixels of the image data + * @param h the height in pixels of the image data + * @param scanlineStride the line stride of the image data + * @param pixelStride the pixel stride of the image data + * @param bandOffsets the offsets of all bands + * @param location the upper-left corner of the Raster + * @return a WritableRaster object with the specified + * DataBuffer, width, height, scanline stride, + * pixel stride and band offsets. + * @throws RasterFormatException if w or h + * is less than or equal to zero, or computing either + * location.x + w or + * location.y + h results in integer + * overflow + * @throws IllegalArgumentException if dataType is not + * one of the supported data types, which are + * DataBuffer.TYPE_BYTE, + * DataBuffer.TYPE_USHORT + * @throws RasterFormatException if dataBuffer has more + * than one bank. + * @throws NullPointerException if dataBuffer is null + */ + public static WritableRaster createInterleavedRaster(DataBuffer dataBuffer, + int w, int h, + int scanlineStride, + int pixelStride, + int bandOffsets[], + Point location) { + if (dataBuffer == null) { + throw new NullPointerException("DataBuffer cannot be null"); + } + if (location == null) { + location = new Point(0, 0); + } + int dataType = dataBuffer.getDataType(); + + PixelInterleavedSampleModel csm = + new PixelInterleavedSampleModel(dataType, w, h, + pixelStride, + scanlineStride, + bandOffsets); + switch(dataType) { + case DataBuffer.TYPE_BYTE: + return new ByteInterleavedRaster(csm, dataBuffer, location); + + case DataBuffer.TYPE_USHORT: + JSUtil.notImplemented("USHORT-interleaved raster"); + return null; // return new ShortInterleavedRaster(csm, dataBuffer, location); -// -// default: -// throw new IllegalArgumentException("Unsupported data type " + -// dataType); -// } -// } -// -// /** -// * Creates a Raster based on a BandedSampleModel with the -// * specified DataBuffer, width, height, scanline stride, bank -// * indices, and band offsets. The number of bands is inferred -// * from bankIndices.length and bandOffsets.length, which must be -// * the same. The upper left corner of the Raster is given by the -// * location argument. If location is null, (0, 0) will be used. -// * @param dataBuffer the DataBuffer that contains the -// * image data -// * @param w the width in pixels of the image data -// * @param h the height in pixels of the image data -// * @param scanlineStride the line stride of the image data -// * @param bankIndices the bank indices for each band -// * @param bandOffsets the offsets of all bands -// * @param location the upper-left corner of the Raster -// * @return a WritableRaster object with the specified -// * DataBuffer, width, height, scanline stride, -// * bank indices and band offsets. -// * @throws RasterFormatException if w or h -// * is less than or equal to zero, or computing either -// * location.x + w or -// * location.y + h results in integer -// * overflow -// * @throws IllegalArgumentException if dataType is not -// * one of the supported data types, which are -// * DataBuffer.TYPE_BYTE, -// * DataBuffer.TYPE_USHORT -// * or DataBuffer.TYPE_INT -// * @throws NullPointerException if dataBuffer is null -// */ -// public static WritableRaster createBandedRaster(DataBuffer dataBuffer, -// int w, int h, -// int scanlineStride, -// int bankIndices[], -// int bandOffsets[], -// Point location) { -// JSToolkit.notImplemented(null); -// if (dataBuffer == null) { -// throw new NullPointerException("DataBuffer cannot be null"); -// } -// if (location == null) { -// location = new Point(0,0); -// } -// int dataType = dataBuffer.getDataType(); -// -// int bands = bankIndices.length; -// if (bandOffsets.length != bands) { -// throw new IllegalArgumentException( -// "bankIndices.length != bandOffsets.length"); -// } -// -// BandedSampleModel bsm = -// new BandedSampleModel(dataType, w, h, -// scanlineStride, -// bankIndices, bandOffsets); -// -// switch(dataType) { -// case DataBuffer.TYPE_BYTE: -// return new ByteBandedRaster(bsm, dataBuffer, location); -// -// case DataBuffer.TYPE_USHORT: -// return new ShortBandedRaster(bsm, dataBuffer, location); -// -// case DataBuffer.TYPE_INT: -// return new SunWritableRaster(bsm, dataBuffer, location); -// -// default: -// throw new IllegalArgumentException("Unsupported data type " + -// dataType); -// } -// } + + default: + throw new IllegalArgumentException("Unsupported data type " + + dataType); + } + } + + /** + * Creates a Raster based on a BandedSampleModel with the + * specified DataBuffer, width, height, scanline stride, bank + * indices, and band offsets. The number of bands is inferred + * from bankIndices.length and bandOffsets.length, which must be + * the same. The upper left corner of the Raster is given by the + * location argument. If location is null, (0, 0) will be used. + * @param dataBuffer the DataBuffer that contains the + * image data + * @param w the width in pixels of the image data + * @param h the height in pixels of the image data + * @param scanlineStride the line stride of the image data + * @param bankIndices the bank indices for each band + * @param bandOffsets the offsets of all bands + * @param location the upper-left corner of the Raster + * @return a WritableRaster object with the specified + * DataBuffer, width, height, scanline stride, + * bank indices and band offsets. + * @throws RasterFormatException if w or h + * is less than or equal to zero, or computing either + * location.x + w or + * location.y + h results in integer + * overflow + * @throws IllegalArgumentException if dataType is not + * one of the supported data types, which are + * DataBuffer.TYPE_BYTE, + * DataBuffer.TYPE_USHORT + * or DataBuffer.TYPE_INT + * @throws NullPointerException if dataBuffer is null + */ + public static WritableRaster createBandedRaster(DataBuffer dataBuffer, + int w, int h, + int scanlineStride, + int bankIndices[], + int bandOffsets[], + Point location) { + if (dataBuffer == null) { + throw new NullPointerException("DataBuffer cannot be null"); + } + if (location == null) { + location = new Point(0,0); + } + int dataType = dataBuffer.getDataType(); + + int bands = bankIndices.length; + if (bandOffsets.length != bands) { + throw new IllegalArgumentException( + "bankIndices.length != bandOffsets.length"); + } + + BandedSampleModel bsm = + new BandedSampleModel(dataType, w, h, + scanlineStride, + bankIndices, bandOffsets); + + switch(dataType) { + case DataBuffer.TYPE_BYTE: + return new ByteBandedRaster(bsm, dataBuffer, location); + + case DataBuffer.TYPE_USHORT: + return new ShortBandedRaster(bsm, dataBuffer, location); + + case DataBuffer.TYPE_INT: + return new SunWritableRaster(bsm, dataBuffer, location); + + default: + throw new IllegalArgumentException("Unsupported data type " + + dataType); + } + } /** * Creates a Raster based on a SinglePixelPackedSampleModel with @@ -783,7 +790,7 @@ public static WritableRaster createPackedRaster(DataBuffer dataBuffer, switch(dataType) { case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_INT: -// case DataBuffer.TYPE_USHORT: + case DataBuffer.TYPE_USHORT: break; default: throw new IllegalArgumentException("Unsupported data type " + @@ -835,7 +842,7 @@ public static WritableRaster createPackedRaster(DataBuffer dataBuffer, int dataType = dataBuffer.getDataType(); if (dataType != DataBuffer.TYPE_BYTE && -// dataType != DataBuffer.TYPE_USHORT && + dataType != DataBuffer.TYPE_USHORT && dataType != DataBuffer.TYPE_INT) { throw new IllegalArgumentException("Unsupported data type " + dataType); @@ -892,26 +899,26 @@ public static Raster createRaster(SampleModel sm, } int dataType = sm.getDataType(); -// if (sm instanceof PixelInterleavedSampleModel) { -// switch(dataType) { -// case DataBuffer.TYPE_BYTE: -// return new ByteInterleavedRaster(sm, db, location); -// -// case DataBuffer.TYPE_USHORT: -// return new ShortInterleavedRaster(sm, db, location); -// } -// } else if (sm instanceof SinglePixelPackedSampleModel) { -// switch(dataType) { -// case DataBuffer.TYPE_BYTE: -// return new ByteInterleavedRaster(sm, db, location); -// -// case DataBuffer.TYPE_USHORT: -// return new ShortInterleavedRaster(sm, db, location); -// -// case DataBuffer.TYPE_INT: -// return new IntegerInterleavedRaster(sm, db, location); -// } -// } else + if (sm instanceof PixelInterleavedSampleModel) { + switch(dataType) { + case DataBuffer.TYPE_BYTE: + return new ByteInterleavedRaster(sm, db, location); + + case DataBuffer.TYPE_USHORT: + return new ShortInterleavedRaster(sm, db, location); + } + } else if (sm instanceof SinglePixelPackedSampleModel) { + switch(dataType) { + case DataBuffer.TYPE_BYTE: + return new ByteInterleavedRaster(sm, db, location); + + case DataBuffer.TYPE_USHORT: + return new ShortInterleavedRaster(sm, db, location); + + case DataBuffer.TYPE_INT: + return new IntegerInterleavedRaster(sm, db, location); + } + } else if (sm instanceof MultiPixelPackedSampleModel && dataType == DataBuffer.TYPE_BYTE && sm.getSampleSize(0) < 8) { @@ -984,27 +991,27 @@ public static WritableRaster createWritableRaster(SampleModel sm, r.秘pix = SunWritableRaster.stealData((DataBufferInt) db, 0); return r; } -// -// if (sm instanceof PixelInterleavedSampleModel) { -// switch(dataType) { -// case DataBuffer.TYPE_BYTE: -// return new ByteInterleavedRaster(sm, db, location); -// -// case DataBuffer.TYPE_USHORT: -// return new ShortInterleavedRaster(sm, db, location); -// } -// } else if (sm instanceof SinglePixelPackedSampleModel) { -// switch(dataType) { -// case DataBuffer.TYPE_BYTE: -// return new ByteInterleavedRaster(sm, db, location); -// -// case DataBuffer.TYPE_USHORT: -// return new ShortInterleavedRaster(sm, db, location); -// -// case DataBuffer.TYPE_INT: -// return new IntegerInterleavedRaster(sm, db, location); -// } -// } else + + if (sm instanceof PixelInterleavedSampleModel) { + switch(dataType) { + case DataBuffer.TYPE_BYTE: + return new ByteInterleavedRaster(sm, db, location); + + case DataBuffer.TYPE_USHORT: + return new ShortInterleavedRaster(sm, db, location); + } + } else if (sm instanceof SinglePixelPackedSampleModel) { + switch(dataType) { + case DataBuffer.TYPE_BYTE: + return new ByteInterleavedRaster(sm, db, location); + + case DataBuffer.TYPE_USHORT: + return new ShortInterleavedRaster(sm, db, location); + + case DataBuffer.TYPE_INT: + return new IntegerInterleavedRaster(sm, db, location); + } + } else if (sm instanceof MultiPixelPackedSampleModel && dataType == DataBuffer.TYPE_BYTE && sm.getSampleSize(0) < 8) { diff --git a/sources/net.sf.j2s.java.core/src/java/io/DataInputStream.java b/sources/net.sf.j2s.java.core/src/java/io/DataInputStream.java index e5955802a..0359d6dc8 100644 --- a/sources/net.sf.j2s.java.core/src/java/io/DataInputStream.java +++ b/sources/net.sf.j2s.java.core/src/java/io/DataInputStream.java @@ -608,6 +608,10 @@ public final String readUTF() throws IOException { return readUTFBytes(this, -1); } + public final static String readUTF(DataInput in) throws IOException { + return readUTFBytes(in, -1); + } + /** * Reads from the stream in a representation of a Unicode * character string encoded in diff --git a/sources/net.sf.j2s.java.core/src/java/io/File.java b/sources/net.sf.j2s.java.core/src/java/io/File.java index 6540fc890..2ffadbf65 100644 --- a/sources/net.sf.j2s.java.core/src/java/io/File.java +++ b/sources/net.sf.j2s.java.core/src/java/io/File.java @@ -235,7 +235,7 @@ private File(String child, File parent) { } private String resolve(String path, String child) { - if (child.length() > 0 && !path.endsWith("/")) + if (child.length() > 0 && !child.startsWith("/") && !path.endsWith("/")) path += "/"; return path + child; } @@ -295,7 +295,7 @@ public File(String parent, String child) { throw new NullPointerException(); } if (parent != null) { - if (parent.equals("")) { + if (parent.equals("") && !child.startsWith("/")) { this.path = resolve(".", child); // fs.resolve(fs.getDefaultParent(), // fs.normalize(child)); } else { @@ -541,7 +541,7 @@ public boolean isAbsolute() { * @see java.io.File#isAbsolute() */ public String getAbsolutePath() { - return this.path;// TODO fs.resolve(this); + return fs.resolve(this); } /** @@ -557,9 +557,8 @@ public String getAbsolutePath() { * @since 1.2 */ public File getAbsoluteFile() { -// String absPath = getAbsolutePath() - return this; -// return new File(absPath, fs.prefixLength(absPath)); + String absPath = getAbsolutePath(); + return new File(absPath, fs.prefixLength(absPath)); } /** @@ -774,12 +773,7 @@ public boolean canWrite() { * method denies read access to the file or directory */ public boolean exists() { - return true; -// SecurityManager security = System.getSecurityManager(); -// if (security != null) { -// security.checkRead(path); -// } -// return ((fs.getBooleanAttributes(this) & FileSystem.BA_EXISTS) != 0); + return fs._exists(this); } /** @@ -800,11 +794,11 @@ public boolean isDirectory() { // if (security != null) { // security.checkRead(path); // } -// return ((fs.getBooleanAttributes(this) & FileSystem.BA_DIRECTORY) -// != 0); + return fs._isDir(this); // - // BH 2019.09.23 return true; - return false; +// return false; +// // BH 2019.09.23 return true; +// // BH 2019.12.31 return true if prefixLength == path.length() } /** @@ -953,12 +947,11 @@ public boolean createNewFile() throws IOException { * delete access to the file */ public boolean delete() { - return true; // SecurityManager security = System.getSecurityManager(); // if (security != null) { // security.checkDelete(path); // } -// return fs.delete(this); + return fs.delete(this); } // // /** @@ -1060,7 +1053,6 @@ public String[] list() { * SecurityManager#checkRead(String)} method denies read access to * the directory */ - @SuppressWarnings({ "rawtypes", "unchecked" }) public String[] list(FilenameFilter filter) { String names[] = list(); if ((names == null) || (filter == null)) { @@ -1932,7 +1924,8 @@ public static File createTempFile(String prefix, String suffix) * * @since 1.2 */ - public int compareTo(File pathname) { + @Override + public int compareTo(File pathname) { return getPath().compareTo(pathname.getPath()); // SwingJS } @@ -1950,7 +1943,8 @@ public int compareTo(File pathname) { * @return true if and only if the objects are the same; * false otherwise */ - public boolean equals(Object obj) { + @Override + public boolean equals(Object obj) { if ((obj != null) && (obj instanceof File)) { return compareTo((File)obj) == 0; } @@ -1971,7 +1965,8 @@ public boolean equals(Object obj) { * * @return A hash code for this abstract pathname */ - public int hashCode() { + @Override + public int hashCode() { try { return this.getCanonicalPath().hashCode() | 1234321; } catch (IOException e) { @@ -1986,7 +1981,8 @@ public int hashCode() { * * @return The string form of this abstract pathname */ - public String toString() { + @Override + public String toString() { return getPath(); } @@ -1997,7 +1993,7 @@ public Path toPath() { result = filePath; if (result == null) { result = FileSystems.getDefault().getPath(path); - ((JSPath) result).秘bytes = 秘bytes; + ((JSPath) result).set(秘bytes, this); filePath = result; } } diff --git a/sources/net.sf.j2s.java.core/src/java/io/FileDescriptor.java b/sources/net.sf.j2s.java.core/src/java/io/FileDescriptor.java index e8a4a575e..f93dfbf4c 100644 --- a/sources/net.sf.j2s.java.core/src/java/io/FileDescriptor.java +++ b/sources/net.sf.j2s.java.core/src/java/io/FileDescriptor.java @@ -28,9 +28,12 @@ package java.io; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; +import swingjs.JSTempFile; + /** * Instances of the file descriptor class serve as an opaque handle * to the underlying machine-specific structure representing an open @@ -48,8 +51,45 @@ */ public final class FileDescriptor { + /** + * SwingJS added + */ File _file; + private int pos, len = -1; + + public void _setPosAndLen(int pos, int len) { + this.pos = pos; + this.len = len; + if (_file.秘bytes == null) + _file.秘bytes = new byte[len]; + else if (len > _file.秘bytes.length) + _file.秘bytes = Arrays.copyOf(_file.秘bytes, len); + } + + public int _getPos() { + return pos; + } + + public int _getLen() { + return len; + } + + private boolean isTempFile; + + /** SwingJS added + * + * @return true if this is a temp file so should not be saved to disk + */ + public boolean _isTempFile() { + return isTempFile; + } + + public void _setTempFile(boolean b) { + isTempFile = b; + } + + private int fd = -1; private long handle = -1; @@ -189,20 +229,21 @@ int decrementAndGetUseCount() { synchronized void attach(Closeable c) { _file = (/** @j2sNative c._file || */null); + isTempFile = _file instanceof JSTempFile; - if (parent == null) { - // first caller gets to do this - parent = c; - } else if (otherParents == null) { - otherParents = new ArrayList<>(); - otherParents.add(parent); - otherParents.add(c); - } else { - otherParents.add(c); - } - } + if (parent == null) { + // first caller gets to do this + parent = c; + } else if (otherParents == null) { + otherParents = new ArrayList<>(); + otherParents.add(parent); + otherParents.add(c); + } else { + otherParents.add(c); + } + } - /** + /** * Cycle through all Closeables sharing this FD and call * close() on each one. * diff --git a/sources/net.sf.j2s.java.core/src/java/io/FileInputStream.java b/sources/net.sf.j2s.java.core/src/java/io/FileInputStream.java index 2c031a801..48a66405d 100644 --- a/sources/net.sf.j2s.java.core/src/java/io/FileInputStream.java +++ b/sources/net.sf.j2s.java.core/src/java/io/FileInputStream.java @@ -62,11 +62,13 @@ class FileInputStream extends InputStream */ private final String path; - private FileChannel channel = null; + private JSFileChannel channel = null; // private final Object closeLock = new Object(); private volatile boolean closed = false; + public File _file; + /** * Creates a FileInputStream by * opening a connection to an actual file, @@ -140,6 +142,7 @@ public FileInputStream(File file) throws FileNotFoundException { // throw new FileNotFoundException("Invalid file path"); // } fd = new FileDescriptor(); + this._file = file; fd.attach(this); path = name; open(file); @@ -217,7 +220,7 @@ private void open(File file) throws FileNotFoundException { */ @Override public int read() throws IOException { - return is.read(); + return (channel == null ? is.read() : channel.read()); } /** @@ -228,7 +231,7 @@ public int read() throws IOException { * @exception IOException If an I/O error has occurred. */ private int readBytes(byte b[], int off, int len) throws IOException { - return is.read(b, off, len); + return channel == null ? is.read(b, off, len) : channel.readBytes(b, off, len); } /** @@ -296,8 +299,7 @@ public int read(byte b[], int off, int len) throws IOException { */ @Override public long skip(long n) throws IOException { - return is.skip(n); - + return channel == null ? is.skip(n) : channel.skip(n); } /** @@ -319,7 +321,7 @@ public long skip(long n) throws IOException { */ @Override public int available() throws IOException { - return is.available(); + return channel == null ? is.available() : channel.available(); } /** @@ -340,17 +342,16 @@ public void close() throws IOException { if (closed) { return; } - is.close(); closed = true; // } - if (channel != null) { - channel.close(); - } - fd.closeAll(new Closeable() { @Override public void close() throws IOException { - //close0(); + if (channel == null) { + is.close(); + } else { + channel.close(); + } } }); } @@ -384,14 +385,21 @@ public final FileDescriptor getFD() throws IOException { * file position. * * @return the file channel associated with this file input stream + * @throws IOException * * @since 1.4 * @spec JSR-51 */ - public FileChannel getChannel() { + public JSFileChannel getChannel() { synchronized (this) { if (channel == null) { - channel = JSFileChannel.open(fd, path, true, false, this); + try { + fd._setPosAndLen(is.pos, is.pos + is.available()); + channel = JSFileChannel.open(fd, path, true, false, this); + is = null; + } catch (IOException e) { + e.printStackTrace(); + } } return channel; } diff --git a/sources/net.sf.j2s.java.core/src/java/io/FileOutputStream.java b/sources/net.sf.j2s.java.core/src/java/io/FileOutputStream.java index 7148bb96c..db9cab869 100644 --- a/sources/net.sf.j2s.java.core/src/java/io/FileOutputStream.java +++ b/sources/net.sf.j2s.java.core/src/java/io/FileOutputStream.java @@ -27,9 +27,11 @@ import java.nio.channels.FileChannel; +import javajs.util.OC; import swingjs.JSFileSystem.JSFileChannel; import swingjs.JSTempFile; -import javajs.util.OC; +import swingjs.JSUtil; + /** * A file output stream is an output stream for writing data to a @@ -65,11 +67,11 @@ class FileOutputStream extends OutputStream */ private final boolean append; - protected OC out; +// protected OC out; /** * The associated channel, initialized lazily. */ - private FileChannel channel; + private JSFileChannel channel; /** * The path of the referenced file @@ -119,7 +121,7 @@ public byte[] _getBytes() { * @see java.lang.SecurityManager#checkWrite(java.lang.String) */ public FileOutputStream(String name) throws FileNotFoundException { - this(name != null ? new File(name) : null, false); + this(name, false); } /** @@ -151,7 +153,10 @@ public FileOutputStream(String name) throws FileNotFoundException { public FileOutputStream(String name, boolean append) throws FileNotFoundException { - this(name != null ? new File(name) : null, append); + this (name == null ? (File) null + : name.startsWith(File.temporaryDirectory) ? + new JSTempFile(name) : new File(name) + , append); } /** @@ -220,10 +225,6 @@ public FileOutputStream(File file, boolean append) { this._file = file; String name = (file != null ? file.getPath() : null); -// SecurityManager security = System.getSecurityManager(); -// if (security != null) { -// security.checkWrite(name); -// } if (name == null) { throw new NullPointerException(); } @@ -234,7 +235,6 @@ public FileOutputStream(File file, boolean append) fd.attach(this); this.append = append; this.path = name; - open(name, append); } @@ -262,7 +262,7 @@ public FileOutputStream(File file, boolean append) * @see java.lang.SecurityManager#checkWrite(java.io.FileDescriptor) */ public FileOutputStream(FileDescriptor fdObj) { - SecurityManager security = System.getSecurityManager(); +// SecurityManager security = System.getSecurityManager(); if (fdObj == null) { throw new NullPointerException(); } @@ -277,24 +277,21 @@ public FileOutputStream(FileDescriptor fdObj) { // SwingJS old: this(new File("output"), false); } + ByteArrayOutputStream bos = null; - /** - * Opens a file, with the specified name, for overwriting or appending. - * @param name name of file to be opened - * @param append whether the file is to be opened in append mode - */ - private void open0(String name, boolean append) - throws FileNotFoundException { - out = new OC(); - ByteArrayOutputStream bos = null; - // append is an interesting option here! -- temp files? - if (append && _file != null && _file.秘bytes != null) { - bos = new ByteArrayOutputStream(); - bos.write(_file.秘bytes, 0, _file.秘bytes.length); - } - out.setParams(null, name, false, bos); - out.setTemp(_file != null && _file instanceof JSTempFile); - } + /** + * Opens a file, with the specified name, for overwriting or appending. + * + * @param name name of file to be opened + * @param append whether the file is to be opened in append mode + */ + private void open0(String name, boolean append) throws FileNotFoundException { + bos = new ByteArrayOutputStream(); + // append is an interesting option here! -- temp files? + if (append && _file != null && _file.秘bytes != null) { + bos.write(_file.秘bytes, 0, _file.秘bytes.length); + } + } // wrap native call to allow instrumentation /** @@ -307,17 +304,6 @@ private void open(String name, boolean append) open0(name, append); } - /** - * Writes the specified byte to this file output stream. - * - * @param b the byte to be written. - * @param append {@code true} if the write operation first - * advances the position to the end of file - */ - private void writeBytes(byte b[], int off, int len) throws IOException { - out.write(b, off, len); - } - /** * Writes the specified byte to this file output stream. Implements * the write method of OutputStream. @@ -327,7 +313,10 @@ private void writeBytes(byte b[], int off, int len) throws IOException { */ @Override public void write(int b) throws IOException { - out.writeByteAsInt(b); + if (channel == null) + bos.write(b); + else + channel.write(b); } /** @@ -339,7 +328,7 @@ public void write(int b) throws IOException { */ @Override public void write(byte b[]) throws IOException { - writeBytes(b, 0, b.length); + write(b, 0, b.length); } /** @@ -353,7 +342,10 @@ public void write(byte b[]) throws IOException { */ @Override public void write(byte b[], int off, int len) throws IOException { - writeBytes(b, off, len); + if (channel == null) + bos.write(b, off, len); + else + channel.writeBytes(b, off, len); } /** @@ -371,28 +363,19 @@ public void write(byte b[], int off, int len) throws IOException { */ @Override public void close() throws IOException { - out.closeChannel(); - 秘bytes = out.toByteArray(); - if (_file != null && 秘bytes != null) - _file.秘bytes = 秘bytes; - if (_file instanceof JSTempFile) - ((JSTempFile) _file).cacheBytes(); - synchronized (closeLock) { - if (closed) { - return; - } - closed = true; - } - - if (channel != null) { - channel.close(); - } - + closed = true; fd.closeAll(new Closeable() { - @Override + @Override public void close() throws IOException { - close0(); - } + if (channel == null) { + bos.close(); + fd._file.秘bytes = 秘bytes = bos.toByteArray(); + if (!fd._isTempFile()) + JSUtil.saveFile(path, 秘bytes, null, null); + } else { + channel.close(); + } + } }); } @@ -433,7 +416,12 @@ public final FileDescriptor getFD() throws IOException { public FileChannel getChannel() { // synchronized (this) { if (channel == null) { - channel = JSFileChannel.open(fd, path, false, true, append, null); + try { + channel = JSFileChannel.open(fd, path, false, true, append, null); + } catch (IOException e) { + // should not be possible + e.printStackTrace(); + } } return channel; // } @@ -462,9 +450,6 @@ protected void finalize() throws IOException { } } - private void close0() throws IOException { - out.closeChannel(); - } // private static native void initIDs(); diff --git a/sources/net.sf.j2s.java.core/src/java/io/FileSystem.java b/sources/net.sf.j2s.java.core/src/java/io/FileSystem.java index b070bdf0c..03c4d4019 100644 --- a/sources/net.sf.j2s.java.core/src/java/io/FileSystem.java +++ b/sources/net.sf.j2s.java.core/src/java/io/FileSystem.java @@ -25,241 +25,284 @@ package java.io; +import swingjs.JSUtil; /** * Package-private abstract class for the local filesystem abstraction. + * + * SwingJS and Java implementation is only for File (including JSTempFile for + * SwingJS). All other "FileSystem" references are to java.nio.FileSystem, via + * FileSystems.getDefault(). + * + * Note that JSFileSystem does not implement java.io.FileSystem. It implements + * java.nio.FileSystem. + * */ class FileSystem { - /** - * Return the FileSystem object representing this platform's local - * filesystem. - */ - public static FileSystem getFileSystem() { - return new FileSystem(); - }; + /** + * Return the FileSystem object representing this platform's local filesystem. + * + * SwingJS just returns this non-abstract class; Java would return a + * WinNTFileSystem or similar subclass. + */ + public static FileSystem getFileSystem() { + return new FileSystem(); + } + /** + * Return the local filesystem's name-separator character. + */ + public char getSeparator() { + return '/'; + } - /* -- Normalization and construction -- */ + /** + * Return the local filesystem's path-separator character. + */ + public char getPathSeparator() { + return '/'; + }; - /** - * Return the local filesystem's name-separator character. - */ - public char getSeparator() { - return '/'; - } + /* -- Attribute accessors -- */ - /** - * Return the local filesystem's path-separator character. - */ - public char getPathSeparator() { - return '/'; - }; + /* Constants for simple boolean attributes */ + public static final int BA_EXISTS = 0x01; + public static final int BA_REGULAR = 0x02; + public static final int BA_DIRECTORY = 0x04; + public static final int BA_HIDDEN = 0x08; -// /** -// * Convert the given pathname string to normal form. If the string is -// * already in normal form then it is simply returned. -// */ -// public abstract String normalize(String path); -// -// /** -// * Compute the length of this pathname string's prefix. The pathname -// * string must be in normal form. -// */ -// public abstract int prefixLength(String path); -// -// /** -// * Resolve the child pathname string against the parent. -// * Both strings must be in normal form, and the result -// * will be in normal form. -// */ -// public abstract String resolve(String parent, String child); -// -// /** -// * Return the parent pathname string to be used when the parent-directory -// * argument in one of the two-argument File constructors is the empty -// * pathname. -// */ -// public abstract String getDefaultParent(); -// -// /** -// * Post-process the given URI path string if necessary. This is used on -// * win32, e.g., to transform "/c:/foo" into "c:/foo". The path string -// * still has slash separators; code in the File class will translate them -// * after this method returns. -// */ -// public abstract String fromURIPath(String path); -// -// -// /* -- Path operations -- */ -// -// /** -// * Tell whether or not the given abstract pathname is absolute. -// */ -// public abstract boolean isAbsolute(File f); -// -// /** -// * Resolve the given abstract pathname into absolute form. Invoked by the -// * getAbsolutePath and getCanonicalPath methods in the File class. -// */ -// public abstract String resolve(File f); -// -// public abstract String canonicalize(String path) throws IOException; -// + public static final int ACCESS_READ = 0x04; + public static final int ACCESS_WRITE = 0x02; + public static final int ACCESS_EXECUTE = 0x01; - /* -- Attribute accessors -- */ + /** + * Delete the file or directory denoted by the given abstract pathname, + * returning true if and only if the operation succeeds. + */ + public boolean delete(File f) { + f.秘bytes = null; + JSUtil.removeCachedFileData(f.toPath().toString()); + return true; + } - /* Constants for simple boolean attributes */ - public static final int BA_EXISTS = 0x01; - public static final int BA_REGULAR = 0x02; - public static final int BA_DIRECTORY = 0x04; - public static final int BA_HIDDEN = 0x08; + /** + * List the elements of the directory denoted by the given abstract pathname. + * Return an array of strings naming the elements of the directory if + * successful; otherwise, return null. + */ + public String[] list(File f) { + return null; + } -// /** -// * Return the simple boolean attributes for the file or directory denoted -// * by the given abstract pathname, or zero if it does not exist or some -// * other I/O error occurs. -// */ -// public abstract int getBooleanAttributes(File f); + /** + * Return the simple boolean attributes for the file or directory denoted by the + * given abstract pathname, or zero if it does not exist or some other I/O error + * occurs. + */ + public int getBooleanAttributes(File file) { + return (_isDir(file) ? BA_DIRECTORY : 0) | (_exists(file) ? BA_EXISTS : 0) | (_regular(file) ? BA_REGULAR : 0) + | (_hidden(file) ? BA_HIDDEN : 0); + } - public static final int ACCESS_READ = 0x04; - public static final int ACCESS_WRITE = 0x02; - public static final int ACCESS_EXECUTE = 0x01; + private boolean _hidden(File file) { + return false; + } -// /** -// * Check whether the file or directory denoted by the given abstract -// * pathname may be accessed by this process. The second argument specifies -// * which access, ACCESS_READ, ACCESS_WRITE or ACCESS_EXECUTE, to check. -// * Return false if access is denied or an I/O error occurs -// */ -// public abstract boolean checkAccess(File f, int access); -// /** -// * Set on or off the access permission (to owner only or to all) to the file -// * or directory denoted by the given abstract pathname, based on the parameters -// * enable, access and oweronly. -// */ -// public abstract boolean setPermission(File f, int access, boolean enable, boolean owneronly); -// -// /** -// * Return the time at which the file or directory denoted by the given -// * abstract pathname was last modified, or zero if it does not exist or -// * some other I/O error occurs. -// */ -// public abstract long getLastModifiedTime(File f); + private boolean _regular(File file) { + return !_isDir(file); + } + + boolean _exists(File file) { + return (file.秘bytes != null || JSUtil.getFileAsBytes(file) != null); + } -// /** -// * Return the length in bytes of the file denoted by the given abstract -// * pathname, or zero if it does not exist, is a directory, or some other -// * I/O error occurs. -// */ -// public abstract long getLength(File f); + boolean _isDir(File file) { + // only an approximation. + return (file.getPrefixLength() == file.path.length() || !_exists(file)); + } + + /** + * Compute the length of this pathname string's prefix. The pathname string must + * be in normal form. + */ + public int prefixLength(String path) { + return path.lastIndexOf("/") + 1; + } + + /** + * Resolve the child pathname string against the parent. Both strings must be in + * normal form, and the result will be in normal form. + */ + public String resolve(File file) { + // TODO: /../,/./? + return file.path; + } + + /** + * Return the parent pathname string to be used when the parent-directory + * argument in one of the two-argument File constructors is the empty pathname. + */ + public String getDefaultParent() { + return "/"; + } + + /** + * Post-process the given URI path string if necessary. This is used on win32, + * e.g., to transform "/c:/foo" into "c:/foo". The path string still has slash + * separators; code in the File class will translate them after this method + * returns. + */ + public String fromURIPath(String path) { + if (path.startsWith("file://")) + path = path.substring(7); + return path; + } + + /** + * Tell whether or not the given abstract pathname is absolute. + */ + public boolean isAbsolute(File f) { + return f.getAbsolutePath() == f.path; + } + + // +// /** +// * Create a new directory denoted by the given abstract pathname, +// * returning true if and only if the operation succeeds. +// */ +// public abstract boolean createDirectory(File f); // +// /** +// * Rename the file or directory denoted by the first abstract pathname to +// * the second abstract pathname, returning true if and only if +// * the operation succeeds. +// */ +// public abstract boolean rename(File f1, File f2); // -// /* -- File operations -- */ +// /** +// * Set the last-modified time of the file or directory denoted by the +// * given abstract pathname, returning true if and only if the +// * operation succeeds. +// */ +// public abstract boolean setLastModifiedTime(File f, long time); // -// /** -// * Create a new empty file with the given pathname. Return -// * true if the file was created and false if a -// * file or directory with the given pathname already exists. Throw an -// * IOException if an I/O error occurs. -// * -// *

-// * The resulting file may have more restrictive access permission -// * on some platforms, if restrictive is true. -// */ -// public abstract boolean createFileExclusively(String pathname, -// boolean restrictive) -// throws IOException; +// /** +// * Mark the file or directory denoted by the given abstract pathname as +// * read-only, returning true if and only if the operation +// * succeeds. +// */ +// public abstract boolean setReadOnly(File f); // -// /** -// * Delete the file or directory denoted by the given abstract pathname, -// * returning true if and only if the operation succeeds. -// */ -// public abstract boolean delete(File f); // - /** - * List the elements of the directory denoted by the given abstract - * pathname. Return an array of strings naming the elements of the - * directory if successful; otherwise, return null. - */ - public String[] list(File f) { - return null; - } +// /* -- Filesystem interface -- */ // -// /** -// * Create a new directory denoted by the given abstract pathname, -// * returning true if and only if the operation succeeds. -// */ -// public abstract boolean createDirectory(File f); +// /** +// * List the available filesystem roots. +// */ +// public abstract File[] listRoots(); // -// /** -// * Rename the file or directory denoted by the first abstract pathname to -// * the second abstract pathname, returning true if and only if -// * the operation succeeds. -// */ -// public abstract boolean rename(File f1, File f2); +// /* -- Disk usage -- */ +// public static final int SPACE_TOTAL = 0; +// public static final int SPACE_FREE = 1; +// public static final int SPACE_USABLE = 2; // -// /** -// * Set the last-modified time of the file or directory denoted by the -// * given abstract pathname, returning true if and only if the -// * operation succeeds. -// */ -// public abstract boolean setLastModifiedTime(File f, long time); +// public abstract long getSpace(File f, int t); // -// /** -// * Mark the file or directory denoted by the given abstract pathname as -// * read-only, returning true if and only if the operation -// * succeeds. -// */ -// public abstract boolean setReadOnly(File f); +// /* -- Basic infrastructure -- */ // +// /** +// * Compare two abstract pathnames lexicographically. +// */ +// public abstract int compare(File f1, File f2); // -// /* -- Filesystem interface -- */ +// /** +// * Compute the hash code of an abstract pathname. +// */ +// public abstract int hashCode(File f); // -// /** -// * List the available filesystem roots. -// */ -// public abstract File[] listRoots(); +// // Flags for enabling/disabling performance optimizations for file +// // name canonicalization +// static boolean useCanonCaches = true; +// static boolean useCanonPrefixCache = true; // -// /* -- Disk usage -- */ -// public static final int SPACE_TOTAL = 0; -// public static final int SPACE_FREE = 1; -// public static final int SPACE_USABLE = 2; +// private static boolean getBooleanProperty(String prop, boolean defaultVal) { +// String val = System.getProperty(prop); +// if (val == null) return defaultVal; +// if (val.equalsIgnoreCase("true")) { +// return true; +// } else { +// return false; +// } +// } // -// public abstract long getSpace(File f, int t); +// static { +// useCanonCaches = getBooleanProperty("sun.io.useCanonCaches", +// useCanonCaches); +// useCanonPrefixCache = getBooleanProperty("sun.io.useCanonPrefixCache", +// useCanonPrefixCache); +// } // -// /* -- Basic infrastructure -- */ +// /** +// * Convert the given pathname string to normal form. If the string is +// * already in normal form then it is simply returned. +// */ +// public abstract String normalize(String path); // -// /** -// * Compare two abstract pathnames lexicographically. -// */ -// public abstract int compare(File f1, File f2); // -// /** -// * Compute the hash code of an abstract pathname. -// */ -// public abstract int hashCode(File f); +// /** +// * Resolve the given abstract pathname into absolute form. Invoked by the +// * getAbsolutePath and getCanonicalPath methods in the File class. +// */ +// public abstract String resolve(File f); // -// // Flags for enabling/disabling performance optimizations for file -// // name canonicalization -// static boolean useCanonCaches = true; -// static boolean useCanonPrefixCache = true; +// public abstract String canonicalize(String path) throws IOException; // -// private static boolean getBooleanProperty(String prop, boolean defaultVal) { -// String val = System.getProperty(prop); -// if (val == null) return defaultVal; -// if (val.equalsIgnoreCase("true")) { -// return true; -// } else { -// return false; -// } -// } +// /** +// * Check whether the file or directory denoted by the given abstract +// * pathname may be accessed by this process. The second argument specifies +// * which access, ACCESS_READ, ACCESS_WRITE or ACCESS_EXECUTE, to check. +// * Return false if access is denied or an I/O error occurs +// */ +// public abstract boolean checkAccess(File f, int access); +// /** +// * Set on or off the access permission (to owner only or to all) to the file +// * or directory denoted by the given abstract pathname, based on the parameters +// * enable, access and oweronly. +// */ +// public abstract boolean setPermission(File f, int access, boolean enable, boolean owneronly); // -// static { -// useCanonCaches = getBooleanProperty("sun.io.useCanonCaches", -// useCanonCaches); -// useCanonPrefixCache = getBooleanProperty("sun.io.useCanonPrefixCache", -// useCanonPrefixCache); -// } +// /** +// * Return the time at which the file or directory denoted by the given +// * abstract pathname was last modified, or zero if it does not exist or +// * some other I/O error occurs. +// */ +// public abstract long getLastModifiedTime(File f); + +// /** +// * Return the length in bytes of the file denoted by the given abstract +// * pathname, or zero if it does not exist, is a directory, or some other +// * I/O error occurs. +// */ +// public abstract long getLength(File f); +// +// +// /* -- File operations -- */ +// +// /** +// * Create a new empty file with the given pathname. Return +// * true if the file was created and false if a +// * file or directory with the given pathname already exists. Throw an +// * IOException if an I/O error occurs. +// * +// *

+// * The resulting file may have more restrictive access permission +// * on some platforms, if restrictive is true. +// */ +// public abstract boolean createFileExclusively(String pathname, +// boolean restrictive) +// throws IOException; +// + } diff --git a/sources/net.sf.j2s.java.core/src/java/io/RandomAccessFile.java b/sources/net.sf.j2s.java.core/src/java/io/RandomAccessFile.java new file mode 100644 index 000000000..440f13bbf --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/io/RandomAccessFile.java @@ -0,0 +1,1174 @@ +/* + * Copyright (c) 1994, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import java.nio.channels.FileChannel; +import swingjs.JSFileSystem.JSFileChannel; +import swingjs.JSTempFile; + + + +/** + * Note - SwingJS replaces a large number of native calls in this class + * with calls to JSFileSystem.JS + * Instances of this class support both reading and writing to a + * random access file. A random access file behaves like a large + * array of bytes stored in the file system. There is a kind of cursor, + * or index into the implied array, called the file pointer; + * input operations read bytes starting at the file pointer and advance + * the file pointer past the bytes read. If the random access file is + * created in read/write mode, then output operations are also available; + * output operations write bytes starting at the file pointer and advance + * the file pointer past the bytes written. Output operations that write + * past the current end of the implied array cause the array to be + * extended. The file pointer can be read by the + * {@code getFilePointer} method and set by the {@code seek} + * method. + *

+ * It is generally true of all the reading routines in this class that + * if end-of-file is reached before the desired number of bytes has been + * read, an {@code EOFException} (which is a kind of + * {@code IOException}) is thrown. If any byte cannot be read for + * any reason other than end-of-file, an {@code IOException} other + * than {@code EOFException} is thrown. In particular, an + * {@code IOException} may be thrown if the stream has been closed. + * + * @author unascribed + * @since JDK1.0 + */ + +public class RandomAccessFile implements DataOutput, DataInput, Closeable { + + private FileDescriptor fd; + private JSFileChannel channel = null; + private boolean rw; + + /** + * The path of the referenced file + * (null if the stream is created with a file descriptor) + */ + private final String path; + + private Object closeLock = new Object(); + private volatile boolean closed = false; + private boolean hasWritten, hasRead; + /** + * _file will be passed on to the FileDescriptor using JavaScript + */ + File _file; + + private static final int O_RDONLY = 1; + private static final int O_RDWR = 2; + private static final int O_SYNC = 4; + private static final int O_DSYNC = 8; + + /** + * Creates a random access file stream to read from, and optionally + * to write to, a file with the specified name. A new + * {@link FileDescriptor} object is created to represent the + * connection to the file. + * + *

The mode argument specifies the access mode with which the + * file is to be opened. The permitted values and their meanings are as + * specified for the RandomAccessFile(File,String) constructor. + * + *

+ * If there is a security manager, its {@code checkRead} method + * is called with the {@code name} argument + * as its argument to see if read access to the file is allowed. + * If the mode allows writing, the security manager's + * {@code checkWrite} method + * is also called with the {@code name} argument + * as its argument to see if write access to the file is allowed. + * + * @param name the system-dependent filename + * @param mode the access mode + * @exception IllegalArgumentException if the mode argument is not equal + * to one of "r", "rw", "rws", or + * "rwd" + * @exception FileNotFoundException + * if the mode is "r" but the given string does not + * denote an existing regular file, or if the mode begins with + * "rw" but the given string does not denote an + * existing, writable regular file and a new regular file of + * that name cannot be created, or if some other error occurs + * while opening or creating the file + * @exception SecurityException if a security manager exists and its + * {@code checkRead} method denies read access to the file + * or the mode is "rw" and the security manager's + * {@code checkWrite} method denies write access to the file + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkRead(java.lang.String) + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + * @revised 1.4 + * @spec JSR-51 + */ + public RandomAccessFile(String name, String mode) + throws FileNotFoundException + { + this(name != null ? new File(name) : null, mode); + } + + /** + * Creates a random access file stream to read from, and optionally to + * write to, the file specified by the {@link File} argument. A new {@link + * FileDescriptor} object is created to represent this file connection. + * + *

The mode argument specifies the access mode + * in which the file is to be opened. The permitted values and their + * meanings are: + * + * + * + * + * + * + * + * + * + * + * + *
ValueMeaning
"r" Open for reading only. Invoking any of the write + * methods of the resulting object will cause an {@link + * java.io.IOException} to be thrown.
"rw" Open for reading and writing. If the file does not already + * exist then an attempt will be made to create it.
"rws" Open for reading and writing, as with "rw", and also + * require that every update to the file's content or metadata be + * written synchronously to the underlying storage device.
"rwd"   Open for reading and writing, as with "rw", and also + * require that every update to the file's content be written + * synchronously to the underlying storage device.
+ * + * The "rws" and "rwd" modes work much like the {@link + * java.nio.channels.FileChannel#force(boolean) force(boolean)} method of + * the {@link java.nio.channels.FileChannel} class, passing arguments of + * true and false, respectively, except that they always + * apply to every I/O operation and are therefore often more efficient. If + * the file resides on a local storage device then when an invocation of a + * method of this class returns it is guaranteed that all changes made to + * the file by that invocation will have been written to that device. This + * is useful for ensuring that critical information is not lost in the + * event of a system crash. If the file does not reside on a local device + * then no such guarantee is made. + * + *

The "rwd" mode can be used to reduce the number of I/O + * operations performed. Using "rwd" only requires updates to the + * file's content to be written to storage; using "rws" requires + * updates to both the file's content and its metadata to be written, which + * generally requires at least one more low-level I/O operation. + * + *

If there is a security manager, its {@code checkRead} method is + * called with the pathname of the {@code file} argument as its + * argument to see if read access to the file is allowed. If the mode + * allows writing, the security manager's {@code checkWrite} method is + * also called with the path argument to see if write access to the file is + * allowed. + * + * @param file the file object + * @param mode the access mode, as described + * above + * @exception IllegalArgumentException if the mode argument is not equal + * to one of "r", "rw", "rws", or + * "rwd" + * @exception FileNotFoundException + * if the mode is "r" but the given file object does + * not denote an existing regular file, or if the mode begins + * with "rw" but the given file object does not denote + * an existing, writable regular file and a new regular file of + * that name cannot be created, or if some other error occurs + * while opening or creating the file + * @exception SecurityException if a security manager exists and its + * {@code checkRead} method denies read access to the file + * or the mode is "rw" and the security manager's + * {@code checkWrite} method denies write access to the file + * @see java.lang.SecurityManager#checkRead(java.lang.String) + * @see java.lang.SecurityManager#checkWrite(java.lang.String) + * @see java.nio.channels.FileChannel#force(boolean) + * @revised 1.4 + * @spec JSR-51 + */ + public RandomAccessFile(File file, String mode) + throws FileNotFoundException + { + if (file == null) { + throw new NullPointerException(); + } + String name = file.getPath(); + int imode = -1; + if (mode.equals("r")) + imode = O_RDONLY; + else if (mode.startsWith("rw")) { + imode = O_RDWR; + rw = true; + if (mode.length() > 2) { + if (mode.equals("rws")) + imode |= O_SYNC; + else if (mode.equals("rwd")) + imode |= O_DSYNC; + else + imode = -1; + } + } + if (imode < 0) + throw new IllegalArgumentException("Illegal mode \"" + mode + + "\" must be one of " + + "\"r\", \"rw\", \"rws\"," + + " or \"rwd\""); +// SecurityManager security = System.getSecurityManager(); +// if (security != null) { +// security.checkRead(name); +// if (rw) { +// security.checkWrite(name); +// } +// } +// if (file.isInvalid()) { +// throw new FileNotFoundException("Invalid file path"); +// } + fd = new FileDescriptor(); + this._file = file; + fd.attach(this); + fd._setTempFile(file instanceof JSTempFile); + if (rw) + fd._setPosAndLen(0, 0); + path = name; + try { + getChannel(); + } catch (IOException e) { + if (e instanceof FileNotFoundException) + throw (FileNotFoundException)e; + e.printStackTrace(); + } + } + + /** + * Returns the opaque file descriptor object associated with this + * stream. + * + * @return the file descriptor object associated with this stream. + * @exception IOException if an I/O error occurs. + * @see java.io.FileDescriptor + */ + public final FileDescriptor getFD() throws IOException { + if (fd != null) { + return fd; + } + throw new IOException(); + } + + /** + * Returns the unique {@link java.nio.channels.FileChannel FileChannel} + * object associated with this file. + * + *

The {@link java.nio.channels.FileChannel#position() + * position} of the returned channel will always be equal to + * this object's file-pointer offset as returned by the {@link + * #getFilePointer getFilePointer} method. Changing this object's + * file-pointer offset, whether explicitly or by reading or writing bytes, + * will change the position of the channel, and vice versa. Changing the + * file's length via this object will change the length seen via the file + * channel, and vice versa. + * + * @return the file channel associated with this file + * @throws IOException + * + * @since 1.4 + * @spec JSR-51 + */ + public final FileChannel getChannel() throws IOException { + synchronized (this) { + if (channel == null) { + channel = JSFileChannel.open(fd, path, true, rw, this); + } + return channel; + } + } + + // 'Read' primitives + + /** + * Reads a byte of data from this file. The byte is returned as an + * integer in the range 0 to 255 ({@code 0x00-0x0ff}). This + * method blocks if no input is yet available. + *

+ * Although {@code RandomAccessFile} is not a subclass of + * {@code InputStream}, this method behaves in exactly the same + * way as the {@link InputStream#read()} method of + * {@code InputStream}. + * + * @return the next byte of data, or {@code -1} if the end of the + * file has been reached. + * @exception IOException if an I/O error occurs. Not thrown if + * end-of-file has been reached. + */ + public int read() throws IOException { + int ret = channel.read(); + if (ret > 0) + hasRead = true; + return ret; + } + + + /** + * Reads up to {@code len} bytes of data from this file into an + * array of bytes. This method blocks until at least one byte of input + * is available. + *

+ * Although {@code RandomAccessFile} is not a subclass of + * {@code InputStream}, this method behaves in exactly the + * same way as the {@link InputStream#read(byte[], int, int)} method of + * {@code InputStream}. + * + * @param b the buffer into which the data is read. + * @param off the start offset in array {@code b} + * at which the data is written. + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end of + * the file has been reached. + * @exception IOException If the first byte cannot be read for any reason + * other than end of file, or if the random access file has been closed, or if + * some other I/O error occurs. + * @exception NullPointerException If {@code b} is {@code null}. + * @exception IndexOutOfBoundsException If {@code off} is negative, + * {@code len} is negative, or {@code len} is greater than + * {@code b.length - off} + */ + public int read(byte b[], int off, int len) throws IOException { + if (len > 0) + hasRead = true; + len = channel.readBytes(b, off, len); + if (len > 0) + hasRead = true; + return len; + } + + /** + * Reads up to {@code b.length} bytes of data from this file + * into an array of bytes. This method blocks until at least one byte + * of input is available. + *

+ * Although {@code RandomAccessFile} is not a subclass of + * {@code InputStream}, this method behaves in exactly the + * same way as the {@link InputStream#read(byte[])} method of + * {@code InputStream}. + * + * @param b the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or + * {@code -1} if there is no more data because the end of + * this file has been reached. + * @exception IOException If the first byte cannot be read for any reason + * other than end of file, or if the random access file has been closed, or if + * some other I/O error occurs. + * @exception NullPointerException If {@code b} is {@code null}. + */ + public int read(byte b[]) throws IOException { + return read(b, 0, b.length); + } + + /** + * Reads {@code b.length} bytes from this file into the byte + * array, starting at the current file pointer. This method reads + * repeatedly from the file until the requested number of bytes are + * read. This method blocks until the requested number of bytes are + * read, the end of the stream is detected, or an exception is thrown. + * + * @param b the buffer into which the data is read. + * @exception EOFException if this file reaches the end before reading + * all the bytes. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void readFully(byte b[]) throws IOException { + readFully(b, 0, b.length); + } + + /** + * Reads exactly {@code len} bytes from this file into the byte + * array, starting at the current file pointer. This method reads + * repeatedly from the file until the requested number of bytes are + * read. This method blocks until the requested number of bytes are + * read, the end of the stream is detected, or an exception is thrown. + * + * @param b the buffer into which the data is read. + * @param off the start offset of the data. + * @param len the number of bytes to read. + * @exception EOFException if this file reaches the end before reading + * all the bytes. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void readFully(byte b[], int off, int len) throws IOException { + int n = 0; + do { + int count = this.read(b, off + n, len - n); + if (count < 0) + throw new EOFException(); + n += count; + } while (n < len); + } + + /** + * Attempts to skip over {@code n} bytes of input discarding the + * skipped bytes. + *

+ * + * This method may skip over some smaller number of bytes, possibly zero. + * This may result from any of a number of conditions; reaching end of + * file before {@code n} bytes have been skipped is only one + * possibility. This method never throws an {@code EOFException}. + * The actual number of bytes skipped is returned. If {@code n} + * is negative, no bytes are skipped. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + * @exception IOException if an I/O error occurs. + */ + @Override + public int skipBytes(int n) throws IOException { + long pos; + long len; + long newpos; + + if (n <= 0) { + return 0; + } + pos = getFilePointer(); + len = length(); + newpos = pos + n; + if (newpos > len) { + newpos = len; + } + seek(newpos); + + /* return the actual number of bytes skipped */ + return (int) (newpos - pos); + } + + // 'Write' primitives + + /** + * Writes the specified byte to this file. The write starts at + * the current file pointer. + * + * @param b the {@code byte} to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + public void write(int b) throws IOException { + if (!rw) + accessDenied(); + channel.write(b); + hasWritten = true; + } + + /** + * Writes {@code b.length} bytes from the specified byte array + * to this file, starting at the current file pointer. + * + * @param b the data. + * @exception IOException if an I/O error occurs. + */ + @Override + public void write(byte b[]) throws IOException { + write(b, 0, b.length); + } + + /** + * Writes {@code len} bytes from the specified byte array + * starting at offset {@code off} to this file. + * + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @exception IOException if an I/O error occurs. + */ + @Override + public void write(byte b[], int off, int len) throws IOException { + if (!rw) + accessDenied(); + channel.writeBytes(b, off, len); + hasWritten = true; + } + + // 'Random access' stuff + + /** + * Returns the current offset in this file. + * + * @return the offset from the beginning of the file, in bytes, + * at which the next read or write occurs. + * @exception IOException if an I/O error occurs. + */ + public long getFilePointer() throws IOException { + return channel.position(); + } + + /** + * Sets the file-pointer offset, measured from the beginning of this + * file, at which the next read or write occurs. The offset may be + * set beyond the end of the file. Setting the offset beyond the end + * of the file does not change the file length. The file length will + * change only by writing after the offset has been set beyond the end + * of the file. + * + * @param pos the offset position, measured in bytes from the + * beginning of the file, at which to set the file + * pointer. + * @exception IOException if {@code pos} is less than + * {@code 0} or if an I/O error occurs. + */ + public void seek(long pos) throws IOException { + if (pos < 0) { + throw new IOException("Negative seek offset"); + } else { + channel.seek(pos); + } + } + + /** + * Returns the length of this file. + * + * @return the length of this file, measured in bytes. + * @exception IOException if an I/O error occurs. + */ + public long length() throws IOException { + return channel.size(); + } + + /** + * Sets the length of this file. + * + *

If the present length of the file as returned by the + * {@code length} method is greater than the {@code newLength} + * argument then the file will be truncated. In this case, if the file + * offset as returned by the {@code getFilePointer} method is greater + * than {@code newLength} then after this method returns the offset + * will be equal to {@code newLength}. + * + *

If the present length of the file as returned by the + * {@code length} method is smaller than the {@code newLength} + * argument then the file will be extended. In this case, the contents of + * the extended portion of the file are not defined. + * + * @param newLength The desired length of the file + * @exception IOException If an I/O error occurs + * @since 1.2 + */ + public void setLength(long newLength) throws IOException { + if (!rw) + accessDenied(); + channel.setLength(newLength); + hasWritten = true; + } + + private void accessDenied() throws IOException { + throw new IOException("Access is denied"); + } + + /** + * Closes this random access file stream and releases any system + * resources associated with the stream. A closed random access + * file cannot perform input or output operations and cannot be + * reopened. + * + *

If this file has an associated channel then the channel is closed + * as well. + * + * @exception IOException if an I/O error occurs. + * + * @revised 1.4 + * @spec JSR-51 + */ + @Override + public void close() throws IOException { + synchronized (closeLock) { + if (closed) { + return; + } + closed = true; + } + if (channel != null) { + if (rw && hasWritten && channel.size() > 0) + channel.doSave(); + fd.closeAll(new Closeable() { + @Override + public void close() throws IOException { + channel.close(); + } + }); + } + + } + + // + // Some "reading/writing Java data types" methods stolen from + // DataInputStream and DataOutputStream. + // + + /** + * Reads a {@code boolean} from this file. This method reads a + * single byte from the file, starting at the current file pointer. + * A value of {@code 0} represents + * {@code false}. Any other value represents {@code true}. + * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the {@code boolean} value read. + * @exception EOFException if this file has reached the end. + * @exception IOException if an I/O error occurs. + */ + @Override + public final boolean readBoolean() throws IOException { + int ch = this.read(); + if (ch < 0) + throw new EOFException(); + return (ch != 0); + } + + /** + * Reads a signed eight-bit value from this file. This method reads a + * byte from the file, starting from the current file pointer. + * If the byte read is {@code b}, where + * 0 <= b <= 255, + * then the result is: + *

+     *     (byte)(b)
+     * 
+ *

+ * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the next byte of this file as a signed eight-bit + * {@code byte}. + * @exception EOFException if this file has reached the end. + * @exception IOException if an I/O error occurs. + */ + @Override + public final byte readByte() throws IOException { + int ch = this.read(); + if (ch < 0) + throw new EOFException(); + return (byte)(ch); + } + + /** + * Reads an unsigned eight-bit number from this file. This method reads + * a byte from this file, starting at the current file pointer, + * and returns that byte. + *

+ * This method blocks until the byte is read, the end of the stream + * is detected, or an exception is thrown. + * + * @return the next byte of this file, interpreted as an unsigned + * eight-bit number. + * @exception EOFException if this file has reached the end. + * @exception IOException if an I/O error occurs. + */ + @Override + public final int readUnsignedByte() throws IOException { + int ch = this.read(); + if (ch < 0) + throw new EOFException(); + return ch; + } + + /** + * Reads a signed 16-bit number from this file. The method reads two + * bytes from this file, starting at the current file pointer. + * If the two bytes read, in order, are + * {@code b1} and {@code b2}, where each of the two values is + * between {@code 0} and {@code 255}, inclusive, then the + * result is equal to: + *

+     *     (short)((b1 << 8) | b2)
+     * 
+ *

+ * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this file, interpreted as a signed + * 16-bit number. + * @exception EOFException if this file reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + @Override + public final short readShort() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (short)((ch1 << 8) + (ch2 << 0)); + } + + /** + * Reads an unsigned 16-bit number from this file. This method reads + * two bytes from the file, starting at the current file pointer. + * If the bytes read, in order, are + * {@code b1} and {@code b2}, where + * 0 <= b1, b2 <= 255, + * then the result is equal to: + *

+     *     (b1 << 8) | b2
+     * 
+ *

+ * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this file, interpreted as an unsigned + * 16-bit integer. + * @exception EOFException if this file reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + @Override + public final int readUnsignedShort() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (ch1 << 8) + (ch2 << 0); + } + + /** + * Reads a character from this file. This method reads two + * bytes from the file, starting at the current file pointer. + * If the bytes read, in order, are + * {@code b1} and {@code b2}, where + * 0 <= b1, b2 <= 255, + * then the result is equal to: + *

+     *     (char)((b1 << 8) | b2)
+     * 
+ *

+ * This method blocks until the two bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next two bytes of this file, interpreted as a + * {@code char}. + * @exception EOFException if this file reaches the end before reading + * two bytes. + * @exception IOException if an I/O error occurs. + */ + @Override + public final char readChar() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (char)((ch1 << 8) + (ch2 << 0)); + } + + /** + * Reads a signed 32-bit integer from this file. This method reads 4 + * bytes from the file, starting at the current file pointer. + * If the bytes read, in order, are {@code b1}, + * {@code b2}, {@code b3}, and {@code b4}, where + * 0 <= b1, b2, b3, b4 <= 255, + * then the result is equal to: + *

+     *     (b1 << 24) | (b2 << 16) + (b3 << 8) + b4
+     * 
+ *

+ * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this file, interpreted as an + * {@code int}. + * @exception EOFException if this file reaches the end before reading + * four bytes. + * @exception IOException if an I/O error occurs. + */ + @Override + public final int readInt() throws IOException { + int ch1 = this.read(); + int ch2 = this.read(); + int ch3 = this.read(); + int ch4 = this.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) + throw new EOFException(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + /** + * Reads a signed 64-bit integer from this file. This method reads eight + * bytes from the file, starting at the current file pointer. + * If the bytes read, in order, are + * {@code b1}, {@code b2}, {@code b3}, + * {@code b4}, {@code b5}, {@code b6}, + * {@code b7}, and {@code b8,} where: + *

+     *     0 <= b1, b2, b3, b4, b5, b6, b7, b8 <=255,
+     * 
+ *

+ * then the result is equal to: + *

+     *     ((long)b1 << 56) + ((long)b2 << 48)
+     *     + ((long)b3 << 40) + ((long)b4 << 32)
+     *     + ((long)b5 << 24) + ((long)b6 << 16)
+     *     + ((long)b7 << 8) + b8
+     * 
+ *

+ * This method blocks until the eight bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next eight bytes of this file, interpreted as a + * {@code long}. + * @exception EOFException if this file reaches the end before reading + * eight bytes. + * @exception IOException if an I/O error occurs. + */ + @Override + public final long readLong() throws IOException { + return ((long)(readInt()) << 32) + (readInt() & 0xFFFFFFFFL); + } + + /** + * Reads a {@code float} from this file. This method reads an + * {@code int} value, starting at the current file pointer, + * as if by the {@code readInt} method + * and then converts that {@code int} to a {@code float} + * using the {@code intBitsToFloat} method in class + * {@code Float}. + *

+ * This method blocks until the four bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next four bytes of this file, interpreted as a + * {@code float}. + * @exception EOFException if this file reaches the end before reading + * four bytes. + * @exception IOException if an I/O error occurs. + * @see java.io.RandomAccessFile#readInt() + * @see java.lang.Float#intBitsToFloat(int) + */ + @Override + public final float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads a {@code double} from this file. This method reads a + * {@code long} value, starting at the current file pointer, + * as if by the {@code readLong} method + * and then converts that {@code long} to a {@code double} + * using the {@code longBitsToDouble} method in + * class {@code Double}. + *

+ * This method blocks until the eight bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return the next eight bytes of this file, interpreted as a + * {@code double}. + * @exception EOFException if this file reaches the end before reading + * eight bytes. + * @exception IOException if an I/O error occurs. + * @see java.io.RandomAccessFile#readLong() + * @see java.lang.Double#longBitsToDouble(long) + */ + @Override + public final double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + /** + * Reads the next line of text from this file. This method successively + * reads bytes from the file, starting at the current file pointer, + * until it reaches a line terminator or the end + * of the file. Each byte is converted into a character by taking the + * byte's value for the lower eight bits of the character and setting the + * high eight bits of the character to zero. This method does not, + * therefore, support the full Unicode character set. + * + *

A line of text is terminated by a carriage-return character + * ({@code '\u005Cr'}), a newline character ({@code '\u005Cn'}), a + * carriage-return character immediately followed by a newline character, + * or the end of the file. Line-terminating characters are discarded and + * are not included as part of the string returned. + * + *

This method blocks until a newline character is read, a carriage + * return and the byte following it are read (to see if it is a newline), + * the end of the file is reached, or an exception is thrown. + * + * @return the next line of text from this file, or null if end + * of file is encountered before even one byte is read. + * @exception IOException if an I/O error occurs. + */ + + @Override + public final String readLine() throws IOException { + StringBuffer input = new StringBuffer(); + int c = -1; + boolean eol = false; + + while (!eol) { + switch (c = read()) { + case -1: + case '\n': + eol = true; + break; + case '\r': + eol = true; + long cur = getFilePointer(); + if ((read()) != '\n') { + seek(cur); + } + break; + default: + input.append((char)c); + break; + } + } + + if ((c == -1) && (input.length() == 0)) { + return null; + } + return input.toString(); + } + + /** + * Reads in a string from this file. The string has been encoded + * using a + * modified UTF-8 + * format. + *

+ * The first two bytes are read, starting from the current file + * pointer, as if by + * {@code readUnsignedShort}. This value gives the number of + * following bytes that are in the encoded string, not + * the length of the resulting string. The following bytes are then + * interpreted as bytes encoding characters in the modified UTF-8 format + * and are converted into characters. + *

+ * This method blocks until all the bytes are read, the end of the + * stream is detected, or an exception is thrown. + * + * @return a Unicode string. + * @exception EOFException if this file reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * @exception UTFDataFormatException if the bytes do not represent + * valid modified UTF-8 encoding of a Unicode string. + * @see java.io.RandomAccessFile#readUnsignedShort() + */ + @Override + public final String readUTF() throws IOException { + return DataInputStream.readUTF(this); + } + + /** + * Writes a {@code boolean} to the file as a one-byte value. The + * value {@code true} is written out as the value + * {@code (byte)1}; the value {@code false} is written out + * as the value {@code (byte)0}. The write starts at + * the current position of the file pointer. + * + * @param v a {@code boolean} value to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void writeBoolean(boolean v) throws IOException { + write(v ? 1 : 0); + //written++; + } + + /** + * Writes a {@code byte} to the file as a one-byte value. The + * write starts at the current position of the file pointer. + * + * @param v a {@code byte} value to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void writeByte(int v) throws IOException { + write(v); + //written++; + } + + /** + * Writes a {@code short} to the file as two bytes, high byte first. + * The write starts at the current position of the file pointer. + * + * @param v a {@code short} to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void writeShort(int v) throws IOException { + write((v >>> 8) & 0xFF); + write((v >>> 0) & 0xFF); + //written += 2; + } + + /** + * Writes a {@code char} to the file as a two-byte value, high + * byte first. The write starts at the current position of the + * file pointer. + * + * @param v a {@code char} value to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void writeChar(int v) throws IOException { + write((v >>> 8) & 0xFF); + write((v >>> 0) & 0xFF); + //written += 2; + } + + /** + * Writes an {@code int} to the file as four bytes, high byte first. + * The write starts at the current position of the file pointer. + * + * @param v an {@code int} to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void writeInt(int v) throws IOException { + write((v >>> 24) & 0xFF); + write((v >>> 16) & 0xFF); + write((v >>> 8) & 0xFF); + write((v >>> 0) & 0xFF); + //written += 4; + } + + /** + * Writes a {@code long} to the file as eight bytes, high byte first. + * The write starts at the current position of the file pointer. + * + * @param v a {@code long} to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void writeLong(long v) throws IOException { + write((int)(v >>> 56) & 0xFF); + write((int)(v >>> 48) & 0xFF); + write((int)(v >>> 40) & 0xFF); + write((int)(v >>> 32) & 0xFF); + write((int)(v >>> 24) & 0xFF); + write((int)(v >>> 16) & 0xFF); + write((int)(v >>> 8) & 0xFF); + write((int)(v >>> 0) & 0xFF); + //written += 8; + } + + /** + * Converts the float argument to an {@code int} using the + * {@code floatToIntBits} method in class {@code Float}, + * and then writes that {@code int} value to the file as a + * four-byte quantity, high byte first. The write starts at the + * current position of the file pointer. + * + * @param v a {@code float} value to be written. + * @exception IOException if an I/O error occurs. + * @see java.lang.Float#floatToIntBits(float) + */ + @Override + public final void writeFloat(float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + /** + * Converts the double argument to a {@code long} using the + * {@code doubleToLongBits} method in class {@code Double}, + * and then writes that {@code long} value to the file as an + * eight-byte quantity, high byte first. The write starts at the current + * position of the file pointer. + * + * @param v a {@code double} value to be written. + * @exception IOException if an I/O error occurs. + * @see java.lang.Double#doubleToLongBits(double) + */ + @Override + public final void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + /** + * Writes the string to the file as a sequence of bytes. Each + * character in the string is written out, in sequence, by discarding + * its high eight bits. The write starts at the current position of + * the file pointer. + * + * @param s a string of bytes to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + @SuppressWarnings("deprecation") + public final void writeBytes(String s) throws IOException { + int len = s.length(); + byte[] b = new byte[len]; + s.getBytes(0, len, b, 0); + channel.writeBytes(b, 0, len); + } + + /** + * Writes a string to the file as a sequence of characters. Each + * character is written to the data output stream as if by the + * {@code writeChar} method. The write starts at the current + * position of the file pointer. + * + * @param s a {@code String} value to be written. + * @exception IOException if an I/O error occurs. + * @see java.io.RandomAccessFile#writeChar(int) + */ + @Override + public final void writeChars(String s) throws IOException { + int clen = s.length(); + int blen = 2*clen; + byte[] b = new byte[blen]; + char[] c = new char[clen]; + s.getChars(0, clen, c, 0); + for (int i = 0, j = 0; i < clen; i++) { + b[j++] = (byte)(c[i] >>> 8); + b[j++] = (byte)(c[i] >>> 0); + } + channel.writeBytes(b, 0, blen); + } + + /** + * Writes a string to the file using + * modified UTF-8 + * encoding in a machine-independent manner. + *

+ * First, two bytes are written to the file, starting at the + * current file pointer, as if by the + * {@code writeShort} method giving the number of bytes to + * follow. This value is the number of bytes actually written out, + * not the length of the string. Following the length, each character + * of the string is output, in sequence, using the modified UTF-8 encoding + * for each character. + * + * @param str a string to be written. + * @exception IOException if an I/O error occurs. + */ + @Override + public final void writeUTF(String str) throws IOException { + DataOutputStream.writeUTF(str, this); + } + + +// static { +// initIDs(); +// } +} diff --git a/sources/net.sf.j2s.java.core/src/java/lang/Class.java b/sources/net.sf.j2s.java.core/src/java/lang/Class.java index 6229bd64e..c968310d4 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/Class.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/Class.java @@ -38,15 +38,21 @@ //import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Objects; - -import javax.xml.bind.annotation.XmlRootElement; +import java.util.function.Function; +import java.util.stream.Collectors; import sun.misc.CompoundEnumeration; +import sun.reflect.annotation.AnnotationParser; +import sun.reflect.annotation.AnnotationSupport; import sun.reflect.annotation.AnnotationType; import swingjs.JSUtil; @@ -100,16 +106,18 @@ * @see java.lang.ClassLoader#defineClass(byte[], int, int) * @since JDK1.0 */ -public final class Class implements java.io.Serializable, java.lang.reflect.GenericDeclaration { -// java.lang.reflect.Type {//, java.lang.reflect.AnnotatedElement { - private static final int ANNOTATION = 0x00002000; - private static final int ENUM = 0x00004000; - private static final int SYNTHETIC = 0x00001000; +public final class Class { + // SwingJS provide a cleaner loading sequence + //implements Serializable, GenericDeclaration, AnnotatedElement { + private static final int ANNOTATION = 0x00002000; + private static final int ENUM = 0x00004000; +// private static final int SYNTHETIC = 0x00001000; - private static class JSClass { - public String __CLASS_NAME__; - } - private JSClass $clazz$; // BH SwingJS + +// private static class JSClass { +// public String __CLASS_NAME__; +// } + public Object $clazz$; // BH SwingJS public String[] $methodList$; // BH see j2sClazz.js Clazz.getClass, from interface parameters // private static native void registerNatives(); @@ -314,9 +322,12 @@ public T newInstance() throws InstantiationException, IllegalAccessException { // if (System.getSecurityManager() != null) { // checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader()); // } + + @SuppressWarnings("unused") + Object c = $clazz$; /** * @j2sNative - * return new this.$clazz$; + * return new c; */ { return null; @@ -404,8 +415,10 @@ public T newInstance() throws InstantiationException, IllegalAccessException { * @since JDK1.1 */ public boolean isInstance(Object obj) { + @SuppressWarnings("unused") + Object c = $clazz$; /** - * @j2sNative return Clazz.instanceOf(obj, this.$clazz$); + * @j2sNative return Clazz.instanceOf(obj, c); */ { return false; @@ -436,18 +449,13 @@ public boolean isInstance(Object obj) { * if the specified Class parameter is null. * @since JDK1.1 */ + @SuppressWarnings("unused") public boolean isAssignableFrom(Class cls) { - /** - * @j2sNative - * - * return(Clazz.instanceOf(cls.$clazz$, this.$clazz$)); - * - */ - { + if (cls == null) return false; - } - - + Object a = cls.$clazz$; + Object me = $clazz$; + return /** @j2sNative Clazz.instanceOf(a, me) || */ false; } @@ -459,8 +467,10 @@ public boolean isAssignableFrom(Class cls) { * {@code false} otherwise. */ public boolean isInterface() { + @SuppressWarnings("unused") + Object me = $clazz$; /** - * @j2sNative return this.$clazz$.$isInterface; + * @j2sNative return me.$isInterface; * */ { @@ -476,8 +486,10 @@ public boolean isInterface() { * @since JDK1.1 */ public boolean isArray() { + @SuppressWarnings("unused") + Object me = $clazz$; /** - * @j2sNative return !!this.$clazz$.__ARRAYTYPE; + * @j2sNative return !!me.__ARRAYTYPE; */ { return false; @@ -532,10 +544,44 @@ public boolean isPrimitive() { * {@code false} otherwise * @since 1.5 */ - public boolean isAnnotation() { - return (getModifiers() & ANNOTATION) != 0; + public boolean isAnnotation() { + @SuppressWarnings("unused") + Object me = $clazz$; + /** + * @j2sNative return !!me.$getMembers$; + * + */ + { + return true; + } } + /** + * Returns true if and only if this class was declared as an enum in the + * source code. + * + * @return true if and only if this class was declared as an enum in the + * source code + * @since 1.5 + */ + public boolean isEnum() { + @SuppressWarnings("unused") + Object me = $clazz$; + /** + * @j2sNative return !!me.$isEnum; + * + */ + { + return false; + } +// // An enum must both directly extend java.lang.Enum and have +// // the ENUM bit set; classes for specialized enum constants +// // don't do the former. +// return //getModifiers() & Modifier.ENUM) != 0 && +// this.getSuperclass() == java.lang.Enum.class; + } + + /** * Returns {@code true} if this class is a synthetic class; returns * {@code false} otherwise. @@ -545,7 +591,8 @@ public boolean isAnnotation() { * @since 1.5 */ public boolean isSynthetic() { - return (getModifiers() & SYNTHETIC) != 0; + return false; +// return (getModifiers() & SYNTHETIC) != 0; } /** @@ -645,6 +692,8 @@ public String getName() { // cache the name to reduce the number of calls into the VM private transient String name; private Field[] fields; + private Class[] implementz; + private int modifiers = -1; private String getName0() { String code = ""; @@ -744,16 +793,34 @@ ClassLoader getClassLoader0() { * loader.getResourceAsStream$S = function(s) { return me.getResourceAsStream$S(s.indexOf("/") == 0 ? s : "/" + s) }; * loader.getResource$S = function(s) { return me.getResource$S(s.indexOf("/") == 0 ? s : "/" + s) }; * loader.getResources$S = function(s) { return me.getResources$S(s) }; + * loader.getParent$ = function() {return null}; */ - { } return loader; - } + /** + * look first in this class's path, then in root + * @param name + * @return + * @throws IOException + */ protected CompoundEnumeration> getResources(String name) throws IOException { - // sorry, just not implemented. - return new CompoundEnumeration((Enumeration[]) new Enumeration[] { null, java.util.Collections.emptyEnumeration() }); + URL url1 = getResource(name); + URL url2 = (name.indexOf("/") == 0 ? null : getClassLoader().getResource(name)); + Enumeration e; + if (url1 == null && url2 == null) { + e = java.util.Collections.emptyEnumeration(); + } else { + List list = new ArrayList(2); + if (url1 != null) + list.add(url1); + if (url2 != null && !url2.equals(url1)) + list.add(url2); + e = java.util.Collections.enumeration(list); + } + return new CompoundEnumeration((Enumeration[]) new Enumeration[] { null, e }); } + /** * Returns an array of {@code TypeVariable} objects that represent the type * variables declared by the generic declaration represented by this @@ -769,15 +836,17 @@ protected CompoundEnumeration> getResources(String name) throws * Specification, 3rd edition * @since 1.5 */ - @Override - public TypeVariable>[] getTypeParameters() { + // GenericDeclaration @Override + public Object//TypeVariable>[] + getTypeParameters() { // if (getGenericSignature() != null) // return (TypeVariable>[]) getGenericInfo().getTypeParameters(); // else - return (TypeVariable>[]) new TypeVariable[0]; + return //(TypeVariable>[]) + new TypeVariable[0]; } - /** + /** fe * Returns the {@code Class} representing the superclass of the entity * (class, interface, primitive type or void) represented by this * {@code Class}. If this {@code Class} represents either the {@code Object} @@ -864,23 +933,22 @@ public Class getSuperclass() { // } /** - * Determines the interfaces implemented by the class or interface - * represented by this object. + * Determines the interfaces implemented by the class or interface represented + * by this object. * *

- * If this object represents a class, the return value is an array - * containing objects representing all interfaces implemented by the class. - * The order of the interface objects in the array corresponds to the order - * of the interface names in the {@code implements} clause of the - * declaration of the class represented by this object. For example, given - * the declaration:

{@code class Shimmer implements FloorWax, - * DessertTopping { ... }}
suppose the value of {@code s} is - * an instance of {@code Shimmer}; the value of the expression:
- * {@code s.getClass().getInterfaces()[0]}
is the - * {@code Class} object that represents interface {@code FloorWax}; and the - * value of:
{@code s.getClass().getInterfaces()[1]} - *
is the {@code Class} object that represents interface - * {@code DessertTopping}. + * If this object represents a class, the return value is an array containing + * objects representing all interfaces implemented by the class. The order of + * the interface objects in the array corresponds to the order of the interface + * names in the {@code implements} clause of the declaration of the class + * represented by this object. For example, given the declaration:
+ * {@code class Shimmer implements FloorWax, DessertTopping { ... }} + *
suppose the value of {@code s} is an instance of + * {@code Shimmer}; the value of the expression:
+ * {@code s.getClass().getInterfaces()[0]}
is the {@code Class} + * object that represents interface {@code FloorWax}; and the value of: + *
{@code s.getClass().getInterfaces()[1]}
is the + * {@code Class} object that represents interface {@code DessertTopping}. * *

* If this object represents an interface, the array contains objects @@ -890,8 +958,8 @@ public Class getSuperclass() { * represented by this object. * *

- * If this object represents a class or interface that implements no - * interfaces, the method returns an array of length 0. + * If this object represents a class or interface that implements no interfaces, + * the method returns an array of length 0. * *

* If this object represents a primitive type or void, the method returns an @@ -899,10 +967,24 @@ public Class getSuperclass() { * * @return an array of interfaces implemented by this class. */ + @SuppressWarnings("unused") public Class[] getInterfaces() { - JSUtil.notImplemented(null); - return new Class[0]; - } + if (implementz == null) { + Class[] a = new Class[0]; + Object me = $clazz$; + Class[] list = /** @j2sNative me.implementz || */null; + if (list != null) { + for (int i = 0, n = list.length; i < n; i++) { + /** + * @j2sNative a.push(Clazz.getClass(list[i])); + */ + } + } + implementz = a; + } + return implementz; + } + // /** // * Returns the {@code Type}s representing the interfaces directly @@ -1002,9 +1084,19 @@ public Class getComponentType() { * @since JDK1.1 */ public int getModifiers() { - return Modifier.PUBLIC | (isEnum() ? Modifier.ENUM : isInterface() ? Modifier.INTERFACE : 0); + return (modifiers >= 0 ? modifiers : Modifier.PUBLIC + | (isEnum() ? ENUM : isInterface() ? Modifier.INTERFACE : 0) + | (isAnnotation() ? ANNOTATION : 0)); } + /** + * From AnnotationParser.JSAnnotationObject + * @param m + */ + public void _setModifiers(int m) { + modifiers = m; + } + /** * Gets the signers of this class. * @@ -1282,9 +1374,12 @@ public String getSimpleName() { return getComponentType().getSimpleName() + "[]"; String name = ""; /** + * .name is for String, Integer, etc. + * * @j2sNative * * name = (this.$clazz$.__ANON ? "" : this.$clazz$.__CLASS_NAME__); + * name || (name = this.$clazz$.name); */ { } @@ -1475,33 +1570,36 @@ private boolean isLocalOrAnonymousClass() { * @since JDK1.1 */ public Class[] getClasses() { - return null; -// // be very careful not to change the stack depth of this -// // checkMemberAccess call for security reasons -// // see java.lang.SecurityManager.checkMemberAccess -//// checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader()); -// -// // Privileged so this implementation can look at DECLARED classes, -// // something the caller might not have privilege to do. The code here -// // is allowed to look at DECLARED classes because (1) it does not hand -// // out anything other than public members and (2) public member access -// // has already been ok'd by the SecurityManager. -// -// Class[] result = (Class[]) java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { + // be very careful not to change the stack depth of this + // checkMemberAccess call for security reasons + // see java.lang.SecurityManager.checkMemberAccess +// checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader()); + + // Privileged so this implementation can look at DECLARED classes, + // something the caller might not have privilege to do. The code here + // is allowed to look at DECLARED classes because (1) it does not hand + // out anything other than public members and (2) public member access + // has already been ok'd by the SecurityManager. + +// Class[] result; +// (Class[]) java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { // public Object run() { -// java.util.List list = new java.util.ArrayList(); -// Class currentClass = Class.this; -// while (currentClass != null) { -// Class[] members = currentClass.getDeclaredClasses(); -// for (int i = 0; i < members.length; i++) { -// if (Modifier.isPublic(members[i].getModifiers())) { -// list.add(members[i]); -// } -// } -// currentClass = currentClass.getSuperclass(); -// } + java.util.List list = new java.util.ArrayList(); + Class currentClass = this; + while (currentClass != null) { + Class[] members = currentClass.getDeclaredClasses(); + for (int i = 0; i < members.length; i++) { + //if (Modifier.isPublic(members[i].getModifiers())) { + // public only + list.add(members[i]); + //} + } + currentClass = currentClass.getSuperclass(); + } // Class[] empty = {}; -// return list.toArray(empty); + // result = + + return list.toArray(new Class[list.size()]); // } // }); // @@ -1556,13 +1654,34 @@ public Field[] getFields() throws SecurityException { if (fields != null) return fields; fields = new Field[0]; - Object cl = /** @j2sNative this.$clazz$ || */null; - Object proto = /** @j2sNative cl.prototype || */null; - addFields(proto, this.fields, 0); - addFields(cl, this.fields, Modifier.STATIC); + addAllFields(fields, true); return fields; } + private void addAllFields(Field[] fields, boolean recurse) { + Object cl = /** @j2sNative this.$clazz$ || */null; + /** @j2sNative + * Clazz._initClass(cl,1,1,0); + */ + addFields(cl, fields, 0); + addFields(cl, fields, Modifier.STATIC); + if (!recurse) + return; + Class c = getSuperclass(); + if (c != null) + c.addAllFields(fields, true); + } + + + Map fieldAnnMap = null; + private Field[] declaredFields; + + // Called by Field to get its + public Map getFieldAnnMap(Object cl) { + initAnnotationsIfNecessary(); + return fieldAnnMap; + } + /** * Returns an array containing {@code Method} objects reflecting all the * public member methods of the class or interface represented by @@ -1725,48 +1844,28 @@ public Field getField(String name) throws NoSuchFieldException, SecurityExceptio // return field; } - private void addFields(Object c, Field[] f, int modifiers) { - String m = null; - /** - * @j2sNative - * - * for (m in c) { - * var isStatic = (typeof this.$clazz$[m] != "undefined"); - * if (!!modifiers != isStatic) - * continue; - * if (this.excludeField$S(m)) continue; - * var o = c[m]; - * switch (typeof o) { - * case "object": if (o && !o.__CLASS_NAME__) continue; - * case "number": case "boolean": case "string": - */ - - addField(f, m, modifiers); - - /** - * @j2sNative - * break; - * } - * } - */ + @SuppressWarnings("null") + private void addFields(Object cl, Field[] f, int modifiers) { + String[] fieldNames = /** @j2sNative Clazz._getFieldNames(cl, !!modifiers) || [] ||*/null; + String[] types = /** @j2sNative Clazz._getFieldTypes(cl, !!modifiers) || [] ||*/null; + for (int i = 0; i < fieldNames.length; i++) { + addField(f, fieldNames[i], modifiers, types[i]); + } } boolean excludeField(String name) { return (name == "prototype" || name.startsWith("__") + || name.startsWith("$") && name.endsWith("$") || name == "$isInterface" || name == "$isEnum" - || name == "implementz" - || name == "$Class$" - || name == "$init$" - || name == "$init0$"); + || name == "implementz"); } - private void addField(Field[] fields, String m, int modifiers) { - - @SuppressWarnings("unused") - Field f = new Field(this, m, modifiers); + private void addField(Field[] fields, String name, int modifiers, String type) { + Field f = new Field(this, name, modifiers); + f._setTypeString(type); /** * @j2sNative * @@ -1865,13 +1964,15 @@ public Method getMethod(String name, Class... paramTypes) throws NoSuchMethod * * o = this.$clazz$; * o = o[qname] || o.prototype && o.prototype[qname]; + * */ + m._setJSMethod(o); if (o == null) throw new NoSuchMethodException(getName() + "." + qname); } return m; // /** -// * @j2sNative +// * @j2sNative // * // * return Clazz.new_(Clazz.load('java.lang.reflect.Method').c$$Class$S$ClassA$Class$ClassA$I, [this, name, // * paramTypes, java.lang.Void, [], 0]); @@ -1974,12 +2075,11 @@ public Constructor getConstructor(Class... parameterTypes) throws NoSuchMe * @since JDK1.1 */ public Class[] getDeclaredClasses() throws SecurityException { - return getClasses(); // // be very careful not to change the stack depth of this // // checkMemberAccess call for security reasons // // see java.lang.SecurityManager.checkMemberAccess //// checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader()); -// return getDeclaredClasses0(); + return getDeclaredClasses0(); } /** @@ -2018,12 +2118,11 @@ public Class[] getDeclaredClasses() throws SecurityException { * @since JDK1.1 */ public Field[] getDeclaredFields() throws SecurityException { - return getFields(); -// // be very careful not to change the stack depth of this -// // checkMemberAccess call for security reasons -// // see java.lang.SecurityManager.checkMemberAccess -//// checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader()); -// return copyFields(privateGetDeclaredFields(false)); + if (declaredFields != null) + return declaredFields; + declaredFields = new Field[0]; + addAllFields(declaredFields, false); + return declaredFields; } /** @@ -2110,6 +2209,9 @@ public Method[] getDeclaredMethods() throws SecurityException { */ public Constructor[] getDeclaredConstructors() throws SecurityException { return getConstructors(); + + // TODO + // // be very careful not to change the stack depth of this // // checkMemberAccess call for security reasons // // see java.lang.SecurityManager.checkMemberAccess @@ -2310,7 +2412,10 @@ public Constructor getDeclaredConstructor(Class... parameterTypes) @SuppressWarnings({ "unused", "null" }) public InputStream getResourceAsStream(String name) { // allows an optional second argument to be a base directory in JavaScript - String clazzName = /** @j2sNative this.$clazz$.__CLASS_NAME$__ || this.$clazz$.__CLASS_NAME__ ||*/ ""; + // "System" class loader will not have this.$clazz$ + String clazzName = /** @j2sNative this.$clazz$ && (this.$clazz$.__CLASS_NAME$__ || this.$clazz$.__CLASS_NAME__)||*/ ""; + if (clazzName == "" && !name.startsWith("/")) + name = "/" + name; Object data = null, fname = null; /** * @j2sNative @@ -2428,7 +2533,7 @@ public InputStream getResourceAsStream(String name) { * this name is found * @since JDK1.1 */ - public java.net.URL getResource(String name) { + public URL getResource(String name) { /** * @j2sNative * @@ -2500,22 +2605,33 @@ public java.net.URL getResource(String name) { /* * Return the Virtual Machine's Class object for the named primitive type. */ - static Class getPrimitiveClass(String name) { + public static Class getPrimitiveOrStringClass(String name) { switch (name) { + case "S": + case "String": + return String.class; + case "Z": case "boolean": return Boolean.TYPE; + case "B": case "byte": return Byte.TYPE; + case "C": case "char": return Character.TYPE; + case "H": case "short": return Short.TYPE; + case "I": case "int": return Integer.TYPE; + case "L": case "long": return Long.TYPE; + case "F": case "float": return Float.TYPE; + case "D": case "double": return Double.TYPE; default: @@ -2590,14 +2706,14 @@ static Class getPrimitiveClass(String name) { // private volatile transient SoftReference declaredPublicFields; // private volatile transient SoftReference declaredPublicMethods; - // Incremented by the VM on each call to JVM TI RedefineClasses() - // that redefines this class or a superclass. - private volatile transient int classRedefinedCount = 0; - - // Value of classRedefinedCount when we last cleared the cached values - // that are sensitive to class redefinition. - private volatile transient int lastRedefinedCount = 0; - +// // Incremented by the VM on each call to JVM TI RedefineClasses() +// // that redefines this class or a superclass. +// private volatile transient int classRedefinedCount = 0; +// +// // Value of classRedefinedCount when we last cleared the cached values +// // that are sensitive to class redefinition. +// private volatile transient int lastRedefinedCount = 0; +// // // Clears cached values that might possibly have been obsoleted by // // a class redefinition. // private void clearCachesOnClassRedefinition() { @@ -2908,39 +3024,66 @@ Method[] getArray() { } } + private Method[] $members$; + // Returns an array of "root" methods. These Method objects must NOT // be propagated to the outside world, but must instead be copied // via ReflectionFactory.copyMethod. private Method[] privateGetPublicMethods() { + if (isAnnotation()) { + if ($members$ == null) { + $members$ = AnnotationParser.JSAnnotationObject.createMethods((Class) this); + } + return $members$; + } + + // TODO this is a nightmare. Method[] ms; if ($methodList$ != null) { + // interface hack ms = new Method[$methodList$.length]; - for (int i = ms.length; -- i >= 0;) { + for (int i = ms.length; --i >= 0;) { ms[i] = new Method(this, $methodList$[i], null, Void.class, null, java.lang.reflect.Modifier.PUBLIC); } return ms; } + ms = new Method[0]; + String attr = null; + Object o = null; /** * @j2sNative * * - var p = this.$clazz$.prototype; - for (var attr in p) { - if (typeof p[attr] == "function" && !p[attr].__CLASS_NAME__ && p[attr] != this.$clazz$[attr] && p[attr].exClazz == this.$clazz$) { - // there are polynormical methods. - ms.push(Clazz.new_(Clazz.load('java.lang.reflect.Method').c$$Class$S$ClassA$Class$ClassA$I, [this, attr, [], java.lang.Void, [], 1])); - } - } - p = this.$clazz$; - for (var attr in p) { - if (typeof p[attr] == "function" && !p[attr].__CLASS_NAME__ && p[attr].exClazz == this.$clazz$) { - ms.push(Clazz.new_(Clazz.load('java.lang.reflect.Method').c$$Class$S$ClassA$Class$ClassA$I, [this, attr, [], java.lang.Void, [], 1 | 8])); - } - } + * var p = this.$clazz$.prototype; + * + * for (attr in p) { o = p[attr]; if (typeof o == "function" && + * o.exName && !o.__CLASS_NAME__ && o != this.$clazz$[attr] && + * o.exClazz == this.$clazz$) { // there are polynormical methods. */ - { - } + + Method m = new Method(this, attr, UNKNOWN_PARAMETERS, Void.class, NO_PARAMETERS, Modifier.PUBLIC); + m._setJSMethod(o); + + /** + * @j2sNative + * + * ms.push(m); + * }} + * p = this.$clazz$; + * for (attr in p) { o = p[attr];if (typeof o == + * "function" && o.exName && !o.__CLASS_NAME__ && + * o.exClazz == this.$clazz$) { + */ + m = new Method(this, attr, UNKNOWN_PARAMETERS, Void.class, NO_PARAMETERS, Modifier.PUBLIC | Modifier.STATIC); + m._setJSMethod(o); + /** + * @j2sNative + * + * ms.push(m); + * }} + */ + return ms; // checkInitted(); @@ -3167,7 +3310,10 @@ private static boolean arrayContentsEq(Object[] a1, Object[] a2) { // // private native Constructor[] getDeclaredConstructors0(boolean publicOnly); // -// private native Class[] getDeclaredClasses0(); + private //native + Class[] getDeclaredClasses0() { + return AnnotationParser.JSAnnotationObject.getDeclaredClasses(this.$clazz$); + } // // private static String argumentTypesToString(Class[] argTypes) { // StringBuilder buf = new StringBuilder(); @@ -3201,7 +3347,7 @@ public static String argumentTypesToString(Class[] parameterTypes) { } // /** use serialVersionUID from JDK 1.1 for interoperability */ - private static final long serialVersionUID = 3206093459760846163L; +// private static final long serialVersionUID = 3206093459760846163L; // // /** // * Class Class is special cased within the Serialization Stream Protocol. @@ -3224,6 +3370,7 @@ public static String argumentTypesToString(Class[] parameterTypes) { // private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; public static final Class[] NO_PARAMETERS = new Class[0]; + public static final Class[] UNKNOWN_PARAMETERS = new Class[0]; // /** // * Returns the assertion status that would be assigned to this class if it @@ -3267,29 +3414,6 @@ public static String argumentTypesToString(Class[] parameterTypes) { // // Retrieves the desired assertion status of this class from the VM // private static native boolean desiredAssertionStatus0(Class clazz); - /** - * Returns true if and only if this class was declared as an enum in the - * source code. - * - * @return true if and only if this class was declared as an enum in the - * source code - * @since 1.5 - */ - public boolean isEnum() { - /** - * @j2sNative return this.$clazz$.$isEnum; - * - */ - { - return false; - } -// // An enum must both directly extend java.lang.Enum and have -// // the ENUM bit set; classes for specialized enum constants -// // don't do the former. -// return //getModifiers() & Modifier.ENUM) != 0 && -// this.getSuperclass() == java.lang.Enum.class; - } - // // Fetches the factory for reflective objects // private static ReflectionFactory getReflectionFactory() { // if (reflectionFactory == null) { @@ -3406,6 +3530,7 @@ Map enumConstantDirectory() { private volatile transient Map enumConstantDirectory = null; + private AnnotationData annotationData; /** * Casts an object to the class or interface represented by this @@ -3463,92 +3588,143 @@ public Class asSubclass(Class clazz) { * @throws NullPointerException {@inheritDoc} * @since 1.5 */ - public A getAnnotation(Class annotationClass) { + // AnnotationElement @Override + public A getAnnotation(Class annotationClass) { Objects.requireNonNull(annotationClass); - JSUtil.notImplemented(null); - return null; - } - -// public XmlRootElement getAnnotation(Class c) { -// -// // TODO Auto-generated method stub -// return null; -// } -// - /** - * @throws NullPointerException - * {@inheritDoc} - * @since 1.5 - */ - public boolean isAnnotationPresent(Class annotationClass) { - if (annotationClass == null) - throw new NullPointerException(); - JSUtil.notImplemented(null); - return false; -// return getAnnotation(annotationClass) != null; + initAnnotationsIfNecessary(); + return (annotations == null ? null : (A) annotations.get(annotationClass)); } - private static Annotation[] EMPTY_ANNOTATIONS_ARRAY; - - private static Annotation[] getEmptyAnnotations() { - return (EMPTY_ANNOTATIONS_ARRAY == null ? EMPTY_ANNOTATIONS_ARRAY = new Annotation[0] : EMPTY_ANNOTATIONS_ARRAY); - } /** * @since 1.5 */ + // AnnotationElement @Override public Annotation[] getAnnotations() { - JSUtil.notImplemented(null); - return getEmptyAnnotations(); -// initAnnotationsIfNecessary(); -// return annotations.values().toArray(EMPTY_ANNOTATIONS_ARRAY); + initAnnotationsIfNecessary(); + return annotations.values().toArray(AnnotationParser.getEmptyAnnotationArray()); } + // AnnotationElement @Override + @SuppressWarnings("hiding") + public T[] getAnnotationsByType(Class annotationClass) { + Objects.requireNonNull(annotationClass); + AnnotationData annotationData = annotationData(); + return AnnotationSupport.getAssociatedAnnotations(annotationData.declaredAnnotations, + this, + annotationClass); + } + + private AnnotationData annotationData() { + // while (true) { // retry loop + AnnotationData annotationData = this.annotationData; +// int classRedefinedCount = this.classRedefinedCount; + if (annotationData != null +// && +// annotationData.redefinedCount == classRedefinedCount + ) { + return annotationData; + } + // null or stale annotationData -> optimistically create new instance + return annotationData = createAnnotationData(0);//classRedefinedCount); + // } + } + + private AnnotationData createAnnotationData(int classRedefinedCount) { + Map, Annotation> declaredAnnotations = + AnnotationParser.parseAnnotations(null, this, false); + Class superClass = getSuperclass(); + Map, Annotation> annotations = null; + if (superClass != null) { + Map, Annotation> superAnnotations = + superClass.annotationData().annotations; + for (Map.Entry, Annotation> e : superAnnotations.entrySet()) { + Class annotationClass = e.getKey(); + if (AnnotationType.getInstance(annotationClass).isInherited()) { + if (annotations == null) { // lazy construction + annotations = new LinkedHashMap<>((Math.max( + declaredAnnotations.size(), + Math.min(12, declaredAnnotations.size() + superAnnotations.size()) + ) * 4 + 2) / 3 + ); + } + annotations.put(annotationClass, e.getValue()); + } + } + } + if (annotations == null) { + // no inherited annotations -> share the Map with declaredAnnotations + annotations = declaredAnnotations; + } else { + // at least one inherited annotation -> declared may override inherited + annotations.putAll(declaredAnnotations); + } + return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount); + } + + // Annotation types cache their internal (AnnotationType) form + + private volatile transient AnnotationType annotationType; + + public void setAnnotationResult(AnnotationType type) { + // SwingJS added + annotationType = type; + } + +// boolean casAnnotationType(AnnotationType oldType, AnnotationType newType) { +// return Atomic.casAnnotationType(this, oldType, newType); +// } + + Class annotationType() { + return (isAnnotation() ? (Class) this : null); + } + + // + public AnnotationType getAnnotationType() { + // SwingJS was package-private + return annotationType; + } + + Map, Annotation> getDeclaredAnnotationMap() { + return annotationData().declaredAnnotations; + } + /** * @since 1.5 */ + // AnnotationElement @Override public Annotation[] getDeclaredAnnotations() { - JSUtil.notImplemented(null); - return getEmptyAnnotations(); -// initAnnotationsIfNecessary(); -// return declaredAnnotations.values().toArray(EMPTY_ANNOTATIONS_ARRAY); + initAnnotationsIfNecessary(); + return declaredAnnotations.values().toArray(AnnotationParser.getEmptyAnnotationArray()); } // // Annotations cache -// private transient Map annotations; -// private transient Map declaredAnnotations; -// -// private synchronized void initAnnotationsIfNecessary() { -//// clearCachesOnClassRedefinition(); -//// if (annotations != null) -//// return; -//// declaredAnnotations = AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this); -//// Class superClass = getSuperclass(); -//// if (superClass == null) { -//// annotations = declaredAnnotations; -//// } else { -//// annotations = new HashMap(); -//// superClass.initAnnotationsIfNecessary(); -//// for (Map.Entry e : superClass.annotations.entrySet()) { -//// Class annotationClass = e.getKey(); -//// if (AnnotationType.getInstance(annotationClass).isInherited()) -//// annotations.put(annotationClass, e.getValue()); -//// } -//// annotations.putAll(declaredAnnotations); -//// } -// } -// -// // Annotation types cache their internal (AnnotationType) form -// - private AnnotationType annotationType; + private transient Map, Annotation> annotations; + private transient Map, Annotation> declaredAnnotations; + + private synchronized void initAnnotationsIfNecessary() { +// clearCachesOnClassRedefinition(); // not allowing redefinition in SwingJS + if (annotations != null) + return; + declaredAnnotations = AnnotationParser.parseAnnotations(null, this, false); + Class superClass = getSuperclass(); + if (superClass == null) { + annotations = declaredAnnotations; + } else { + annotations = new HashMap<>(); + superClass.initAnnotationsIfNecessary(); + for (Map.Entry, Annotation> e : superClass.annotations.entrySet()) { + Class annotationClass = e.getKey(); + if (AnnotationType.getInstance(annotationClass).isInherited()) + annotations.put(annotationClass, e.getValue()); + } + annotations.putAll(declaredAnnotations); + } + } void setAnnotationType(AnnotationType type) { annotationType = type; } - AnnotationType getAnnotationType() { - return annotationType; - } - @SuppressWarnings("null") @Override public int hashCode() { @@ -3566,10 +3742,12 @@ public boolean equals(Object o) { /** * @j2sNative * - * return o.__CLASS_NAME__ == "java.lang.Class" && o.$clazz$ == this.$clazz$; + * return o && o.__CLASS_NAME__ == "java.lang.Class" && o.$clazz$ == this.$clazz$; * */ + { return false; + } } /** @@ -3609,4 +3787,57 @@ public static Class getJ2SSuperclassFor(Class cl) { return c; } + // annotation data that might get invalidated when JVM TI RedefineClasses() is called + private static class AnnotationData { + final Map, Annotation> annotations; + final Map, Annotation> declaredAnnotations; + + // Value of classRedefinedCount when we created this AnnotationData instance + //final int redefinedCount; + + AnnotationData(Map, Annotation> annotations, + Map, Annotation> declaredAnnotations, + int redefinedCount) { + this.annotations = annotations; + this.declaredAnnotations = declaredAnnotations; + // this.redefinedCount = redefinedCount; + } + } + + // SwingJS from AnnotatedElement + @SuppressWarnings("hiding") + public T getDeclaredAnnotation(Class annotationClass) { + Objects.requireNonNull(annotationClass); + // Loop over all directly-present annotations looking for a matching one + for (Annotation annotation : getDeclaredAnnotations()) { + if (annotationClass.equals(annotation.annotationType())) { + // More robust to do a dynamic cast at runtime instead + // of compile-time only. + return annotationClass.cast(annotation); + } + } + return null; + } + + // SwingJS from AnnotatedElement + @SuppressWarnings("hiding") + public T[] getDeclaredAnnotationsByType(Class annotationClass) { + Objects.requireNonNull(annotationClass); + return AnnotationSupport. + getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()). + collect(Collectors.toMap(Annotation::annotationType, + Function.identity(), + ((first,second) -> first), + LinkedHashMap::new)), + annotationClass); + } + + // SwingJS from AnnotatedElement + public boolean isAnnotationPresent(Class annotationClass) { + if (annotationClass == null) + throw new NullPointerException(); + return getAnnotation(annotationClass) != null; + } + + } diff --git a/sources/net.sf.j2s.java.core/src/java/lang/System.java b/sources/net.sf.j2s.java.core/src/java/lang/System.java new file mode 100644 index 000000000..81de44dfa --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/lang/System.java @@ -0,0 +1,1431 @@ +/* + * Note: This class is not used; it was the basis for j2sClazz.js System + * Copyright (c) 1994, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.lang; + +import java.io.Console; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +//import java.security.AccessController; +//import java.security.PrivilegedAction; +//import java.security.AllPermission; +import java.nio.channels.Channel; +import java.nio.channels.spi.SelectorProvider; +//import java.security.AccessControlContext; +import java.util.Properties; + +//import sun.reflect.CallerSensitive; +//import sun.reflect.Reflection; +//import sun.security.util.SecurityConstants; +//import sun.reflect.annotation.AnnotationType; +import swingjs.JSUtil; + +/** + * The System class contains several useful class fields and + * methods. It cannot be instantiated. + * + *

+ * Among the facilities provided by the System class are standard + * input, standard output, and error output streams; access to externally + * defined properties and environment variables; a means of loading files and + * libraries; and a utility method for quickly copying a portion of an array. + * + * @author unascribed + * @since JDK1.0 + */ +public final class System { + +// /* register the natives via the static initializer. +// * +// * VM will invoke the initializeSystemClass method to complete +// * the initialization for this class separated from clinit. +// * Note that to use properties set by the VM, see the constraints +// * described in the initializeSystemClass method. +// */ +// private static native void registerNatives(); +// static { +// registerNatives(); +// } + + /** Don't let anyone instantiate this class */ + private System() { + } + + /** + * The "standard" input stream. This stream is already open and ready to supply + * input data. Typically this stream corresponds to keyboard input or another + * input source specified by the host environment or user. + */ + public static InputStream in = null; + + /** + * The "standard" output stream. This stream is already open and ready to accept + * output data. Typically this stream corresponds to display output or another + * output destination specified by the host environment or user. + *

+ * For simple stand-alone Java applications, a typical way to write a line of + * output data is:

+ * + *
+	 * System.out.println(data)
+	 * 
+ * + *
+ *

+ * See the println methods in class PrintStream. + * + * @see java.io.PrintStream#println() + * @see java.io.PrintStream#println(boolean) + * @see java.io.PrintStream#println(char) + * @see java.io.PrintStream#println(char[]) + * @see java.io.PrintStream#println(double) + * @see java.io.PrintStream#println(float) + * @see java.io.PrintStream#println(int) + * @see java.io.PrintStream#println(long) + * @see java.io.PrintStream#println(java.lang.Object) + * @see java.io.PrintStream#println(java.lang.String) + */ + public static PrintStream out = null; + + /** + * The "standard" error output stream. This stream is already open and ready to + * accept output data. + *

+ * Typically this stream corresponds to display output or another output + * destination specified by the host environment or user. By convention, this + * output stream is used to display error messages or other information that + * should come to the immediate attention of a user even if the principal output + * stream, the value of the variable out, has been redirected to a + * file or other destination that is typically not continuously monitored. + */ + public static PrintStream err = null; + + /* + * The security manager for the system. + */ +// private static volatile SecurityManager security = null; + + /** + * Reassigns the "standard" input stream. + * + *

+ * First, if there is a security manager, its checkPermission + * method is called with a RuntimePermission("setIO") permission to + * see if it's ok to reassign the "standard" input stream. + *

+ * + * @param in the new standard input stream. + * + * @throws SecurityException if a security manager exists and its + * checkPermission method doesn't allow + * reassigning of the standard input stream. + * + * @see SecurityManager#checkPermission + * @see java.lang.RuntimePermission + * + * @since JDK1.1 + */ + public static void setIn(InputStream in) { + //checkIO(); +// setIn0(in); + System.in = in; + } + + /** + * Reassigns the "standard" output stream. + * + *

+ * First, if there is a security manager, its checkPermission + * method is called with a RuntimePermission("setIO") permission to + * see if it's ok to reassign the "standard" output stream. + * + * @param out the new standard output stream + * + * @throws SecurityException if a security manager exists and its + * checkPermission method doesn't allow + * reassigning of the standard output stream. + * + * @see SecurityManager#checkPermission + * @see java.lang.RuntimePermission + * + * @since JDK1.1 + */ + public static void setOut(PrintStream out) { + //checkIO(); +// setOut0(out); + System.out = out; + } + + /** + * Reassigns the "standard" error output stream. + * + *

+ * First, if there is a security manager, its checkPermission + * method is called with a RuntimePermission("setIO") permission to + * see if it's ok to reassign the "standard" error output stream. + * + * @param err the new standard error output stream. + * + * @throws SecurityException if a security manager exists and its + * checkPermission method doesn't allow + * reassigning of the standard error output stream. + * + * @see SecurityManager#checkPermission + * @see java.lang.RuntimePermission + * + * @since JDK1.1 + */ + public static void setErr(PrintStream err) { + //checkIO(); + System.err = err; +// setErr0(err); + } + + private static volatile Console cons = null; + + /** + * Returns the unique {@link java.io.Console Console} object associated with the + * current Java virtual machine, if any. + * + * @return The system console, if any, otherwise null. + * + * @since 1.6 + */ + public static Console console() { + JSUtil.notImplemented(null); +// if (cons == null) { +// synchronized (System.class) { +// cons = sun.misc.SharedSecrets.getJavaIOAccess().console(); +// } +// } + return cons; + } + + /** + * Returns the channel inherited from the entity that created this Java virtual + * machine. + * + *

+ * This method returns the channel obtained by invoking the + * {@link java.nio.channels.spi.SelectorProvider#inheritedChannel + * inheritedChannel} method of the system-wide default + * {@link java.nio.channels.spi.SelectorProvider} object. + *

+ * + *

+ * In addition to the network-oriented channels described in + * {@link java.nio.channels.spi.SelectorProvider#inheritedChannel + * inheritedChannel}, this method may return other kinds of channels in the + * future. + * + * @return The inherited channel, if any, otherwise null. + * + * @throws IOException If an I/O error occurs + * + * @throws SecurityException If a security manager is present and it does not + * permit access to the channel. + * + * @since 1.5 + */ + public static Channel inheritedChannel() throws IOException { + return null;//SelectorProvider.provider().inheritedChannel(); + } + +// private static void checkIO() { +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPermission(new RuntimePermission("setIO")); +// } +// } +// +// private static void setIn0(InputStream in) { +// } +// +// private static void setOut0(PrintStream out) { +// +// } +// +// private static void setErr0(PrintStream err) { +// +// } +// + /** + * Sets the System security. + * + *

+ * If there is a security manager already installed, this method first calls the + * security manager's checkPermission method with a + * RuntimePermission("setSecurityManager") permission to ensure + * it's ok to replace the existing security manager. This may result in throwing + * a SecurityException. + * + *

+ * Otherwise, the argument is established as the current security manager. If + * the argument is null and no security manager has been + * established, then no action is taken and the method simply returns. + * + * @param s the security manager. + * @exception SecurityException if the security manager has already been set and + * its checkPermission method doesn't + * allow it to be replaced. + * @see #getSecurityManager + * @see SecurityManager#checkPermission + * @see java.lang.RuntimePermission + */ + public static void setSecurityManager(final SecurityManager s) { +// try { +// s.checkPackageAccess("java.lang"); +// } catch (Exception e) { +// // no-op +// } +// setSecurityManager0(s); + } + +// private static synchronized +// void setSecurityManager0(final SecurityManager s) { +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// // ask the currently installed security manager if we +// // can replace it. +// sm.checkPermission(new RuntimePermission +// ("setSecurityManager")); +// } +// +// if ((s != null) && (s.getClass().getClassLoader() != null)) { +// // New security manager class is not on bootstrap classpath. +// // Cause policy to get initialized before we install the new +// // security manager, in order to prevent infinite loops when +// // trying to initialize the policy (which usually involves +// // accessing some security and/or system properties, which in turn +// // calls the installed security manager's checkPermission method +// // which will loop infinitely if there is a non-system class +// // (in this case: the new security manager class) on the stack). +// AccessController.doPrivileged(new PrivilegedAction() { +// public Object run() { +// s.getClass().getProtectionDomain().implies +// (SecurityConstants.ALL_PERMISSION); +// return null; +// } +// }); +// } +// +// security = s; +// } + + /** + * Gets the system security interface. + * + * @return if a security manager has already been established for the current + * application, then that security manager is returned; otherwise, + * null is returned. + * @see #setSecurityManager + */ + public static SecurityManager getSecurityManager() { + return null; +// return security; + } + + /** + * Returns the current time in milliseconds. Note that while the unit of time of + * the return value is a millisecond, the granularity of the value depends on + * the underlying operating system and may be larger. For example, many + * operating systems measure time in units of tens of milliseconds. + * + *

+ * See the description of the class Date for a discussion of slight + * discrepancies that may arise between "computer time" and coordinated + * universal time (UTC). + * + * @return the difference, measured in milliseconds, between the current time + * and midnight, January 1, 1970 UTC. + * @see java.util.Date + */ + public static /* native */ long currentTimeMillis() { + /** + * @j2sNative return new Date().getTime(); + */ + { + return 0; + } + } + + /** + * Returns the current value of the running Java Virtual Machine's + * high-resolution time source, in nanoseconds. + * + *

+ * This method can only be used to measure elapsed time and is not related to + * any other notion of system or wall-clock time. The value returned represents + * nanoseconds since some fixed but arbitrary origin time (perhaps in the + * future, so values may be negative). The same origin is used by all + * invocations of this method in an instance of a Java virtual machine; other + * virtual machine instances are likely to use a different origin. + * + *

+ * This method provides nanosecond precision, but not necessarily nanosecond + * resolution (that is, how frequently the value changes) - no guarantees are + * made except that the resolution is at least as good as that of + * {@link #currentTimeMillis()}. + * + *

+ * Differences in successive calls that span greater than approximately 292 + * years (263 nanoseconds) will not correctly compute elapsed time + * due to numerical overflow. + * + *

+ * The values returned by this method become meaningful only when the difference + * between two such values, obtained within the same instance of a Java virtual + * machine, is computed. + * + *

+ * For example, to measure how long some code takes to execute: + * + *

+	 * {
+	 * 	@code
+	 * 	long startTime = System.nanoTime();
+	 * // ... the code being measured ...
+	 * 	long estimatedTime = System.nanoTime() - startTime;
+	 * }
+	 * 
+ * + *

+ * To compare two nanoTime values + * + *

+	 *  {@code
+	 * long t0 = System.nanoTime();
+	 * ...
+	 * long t1 = System.nanoTime();}
+	 * 
+ * + * one should use {@code t1 - t0 < 0}, not {@code t1 < t0}, because of the + * possibility of numerical overflow. + * + * @return the current value of the running Java Virtual Machine's + * high-resolution time source, in nanoseconds + * @since 1.5 + */ + public static /* native */ long nanoTime() { + /** + * @j2sNative return Math.round(window.performance.now() * 1e6); + */ + { + return 0; + } + } + + /** + * Copies an array from the specified source array, beginning at the specified + * position, to the specified position of the destination array. A subsequence + * of array components are copied from the source array referenced by + * src to the destination array referenced by dest. + * The number of components copied is equal to the length argument. + * The components at positions srcPos through + * srcPos+length-1 in the source array are copied into positions + * destPos through destPos+length-1, respectively, of + * the destination array. + *

+ * If the src and dest arguments refer to the same + * array object, then the copying is performed as if the components at positions + * srcPos through srcPos+length-1 were first copied to + * a temporary array with length components and then the contents + * of the temporary array were copied into positions destPos + * through destPos+length-1 of the destination array. + *

+ * If dest is null, then a + * NullPointerException is thrown. + *

+ * If src is null, then a + * NullPointerException is thrown and the destination array is not + * modified. + *

+ * Otherwise, if any of the following is true, an + * ArrayStoreException is thrown and the destination is not + * modified: + *

    + *
  • The src argument refers to an object that is not an array. + *
  • The dest argument refers to an object that is not an array. + *
  • The src argument and dest argument refer to + * arrays whose component types are different primitive types. + *
  • The src argument refers to an array with a primitive + * component type and the dest argument refers to an array with a + * reference component type. + *
  • The src argument refers to an array with a reference + * component type and the dest argument refers to an array with a + * primitive component type. + *
+ *

+ * Otherwise, if any of the following is true, an + * IndexOutOfBoundsException is thrown and the destination is not + * modified: + *

    + *
  • The srcPos argument is negative. + *
  • The destPos argument is negative. + *
  • The length argument is negative. + *
  • srcPos+length is greater than src.length, the + * length of the source array. + *
  • destPos+length is greater than dest.length, the + * length of the destination array. + *
+ *

+ * Otherwise, if any actual component of the source array from position + * srcPos through srcPos+length-1 cannot be converted + * to the component type of the destination array by assignment conversion, an + * ArrayStoreException is thrown. In this case, let k + * be the smallest nonnegative integer less than length such that + * src[srcPos+k] cannot be converted to the + * component type of the destination array; when the exception is thrown, source + * array components from positions srcPos through + * srcPos+k-1 will already have been copied to + * destination array positions destPos through + * destPos+k-1 and no other positions of the + * destination array will have been modified. (Because of the restrictions + * already itemized, this paragraph effectively applies only to the situation + * where both arrays have component types that are reference types.) + * + * @param src the source array. + * @param srcPos starting position in the source array. + * @param dest the destination array. + * @param destPos starting position in the destination data. + * @param length the number of array elements to be copied. + * @exception IndexOutOfBoundsException if copying would cause access of data + * outside array bounds. + * @exception ArrayStoreException if an element in the src + * array could not be stored into the + * dest array because of a + * type mismatch. + * @exception NullPointerException if either src or + * dest is null. + */ + public static /* native */ void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) { + /** + * @j2sNative if (src !== dest || srcPos > destPos) { for (var i = length; --i + * >= 0;) dest[destPos++] = src[srcPos++]; } else { destPos += + * length; srcPos += length; for (var i = length; --i >= 0;) + * src[--destPos] = src[--srcPos]; } + */ + } + + /** + * Returns the same hash code for the given object as would be returned by the + * default method hashCode(), whether or not the given object's class overrides + * hashCode(). The hash code for the null reference is zero. + * + * @param x object for which the hashCode is to be calculated + * @return the hashCode + * @since JDK1.1 + */ + public static /* native */ int identityHashCode(Object x) { + /** + * @j2sNative + * return Clazz._identityHashCode(x); + */ + { + return 0; + } + + } + + /** + * System properties. The following properties are guaranteed to be defined: + *

+ *
java.version + *
Java version number + *
java.vendor + *
Java vendor specific string + *
java.vendor.url + *
Java vendor URL + *
java.home + *
Java installation directory + *
java.class.version + *
Java class version number + *
java.class.path + *
Java classpath + *
os.name + *
Operating System Name + *
os.arch + *
Operating System Architecture + *
os.version + *
Operating System Version + *
file.separator + *
File separator ("/" on Unix) + *
path.separator + *
Path separator (":" on Unix) + *
line.separator + *
Line separator ("\n" on Unix) + *
user.name + *
User account name + *
user.home + *
User home directory + *
user.dir + *
User's current working directory + *
+ */ + + private static Properties props; + + // private static /*native*/ Properties initProperties(Properties props); + + /** + * Determines the current system properties. + *

+ * First, if there is a security manager, its checkPropertiesAccess + * method is called with no arguments. This may result in a security exception. + *

+ * The current set of system properties for use by the + * {@link #getProperty(String)} method is returned as a Properties + * object. If there is no current set of system properties, a set of system + * properties is first created and initialized. This set of system properties + * always includes values for the following keys: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
KeyDescription of Associated Value
java.versionJava Runtime Environment version
java.vendorJava Runtime Environment vendor
java.vendor.urlJava vendor URL
java.homeJava installation directory
java.vm.specification.versionJava Virtual Machine specification version
java.vm.specification.vendorJava Virtual Machine specification vendor
java.vm.specification.nameJava Virtual Machine specification name
java.vm.versionJava Virtual Machine implementation version
java.vm.vendorJava Virtual Machine implementation vendor
java.vm.nameJava Virtual Machine implementation name
java.specification.versionJava Runtime Environment specification version
java.specification.vendorJava Runtime Environment specification vendor
java.specification.nameJava Runtime Environment specification name
java.class.versionJava class format version number
java.class.pathJava class path
java.library.pathList of paths to search when loading libraries
java.io.tmpdirDefault temp file path
java.compilerName of JIT compiler to use
java.ext.dirsPath of extension directory or directories Deprecated. This + * property, and the mechanism which implements it, may be removed in a future + * release.
os.nameOperating system name
os.archOperating system architecture
os.versionOperating system version
file.separatorFile separator ("/" on UNIX)
path.separatorPath separator (":" on UNIX)
line.separatorLine separator ("\n" on UNIX)
user.nameUser's account name
user.homeUser's home directory
user.dirUser's current working directory
+ *

+ * Multiple paths in a system property value are separated by the path separator + * character of the platform. + *

+ * Note that even if the security manager does not permit the + * getProperties operation, it may choose to permit the + * {@link #getProperty(String)} operation. + * + * @return the system properties + * @exception SecurityException if a security manager exists and its + * checkPropertiesAccess method + * doesn't allow access to the system properties. + * @see #setProperties + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkPropertiesAccess() + * @see java.util.Properties + */ + public static Properties getProperties() { +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPropertiesAccess(); +// } +// + return getProps(); + } + + private static Properties getProps() { + if (props == null) { + /** + * @j2sNative + * + * C$.props = Clazz.new_("java.util.Properties"); + * Clazz._setSystemProperties(C$.props); + */ + lineSeparator = props.getProperty("line.separator"); + } + return props; + } + + /** + * Returns the system-dependent line separator string. It always returns the + * same value - the initial value of the {@linkplain #getProperty(String) system + * property} {@code line.separator}. + * + *

+ * On UNIX systems, it returns {@code "\n"}; on Microsoft Windows systems it + * returns {@code "\r\n"}. + * + * @return the system-dependent line separator string + * @since 1.7 + */ + public static String lineSeparator() { + if (lineSeparator == null) + getProps(); + return lineSeparator; + } + + private static String lineSeparator = "\n"; + + /** + * Sets the system properties to the Properties argument. + *

+ * First, if there is a security manager, its checkPropertiesAccess + * method is called with no arguments. This may result in a security exception. + *

+ * The argument becomes the current set of system properties for use by the + * {@link #getProperty(String)} method. If the argument is null, + * then the current set of system properties is forgotten. + * + * @param props the new system properties. + * @exception SecurityException if a security manager exists and its + * checkPropertiesAccess method + * doesn't allow access to the system properties. + * @see #getProperties + * @see java.util.Properties + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkPropertiesAccess() + */ + public static void setProperties(Properties props) { +// JSUtil.notImplemented(null); +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPropertiesAccess(); +// } + System.props = props; + } + + /** + * Gets the system property indicated by the specified key. + *

+ * First, if there is a security manager, its checkPropertyAccess + * method is called with the key as its argument. This may result in a + * SecurityException. + *

+ * If there is no current set of system properties, a set of system properties + * is first created and initialized in the same manner as for the + * getProperties method. + * + * @param key the name of the system property. + * @return the string value of the system property, or null if + * there is no property with that key. + * + * @exception SecurityException if a security manager exists and its + * checkPropertyAccess method + * doesn't allow access to the specified + * system property. + * @exception NullPointerException if key is null. + * @exception IllegalArgumentException if key is empty. + * @see #setProperty + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkPropertyAccess(java.lang.String) + * @see java.lang.System#getProperties() + */ + public static String getProperty(String key) { + checkKey(key); +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPropertyAccess(key); +// } +// + return getProps().getProperty(key); + } + + /** + * Gets the system property indicated by the specified key. + *

+ * First, if there is a security manager, its checkPropertyAccess + * method is called with the key as its argument. + *

+ * If there is no current set of system properties, a set of system properties + * is first created and initialized in the same manner as for the + * getProperties method. + * + * @param key the name of the system property. + * @param def a default value. + * @return the string value of the system property, or the default value if + * there is no property with that key. + * + * @exception SecurityException if a security manager exists and its + * checkPropertyAccess method + * doesn't allow access to the specified + * system property. + * @exception NullPointerException if key is null. + * @exception IllegalArgumentException if key is empty. + * @see #setProperty + * @see java.lang.SecurityManager#checkPropertyAccess(java.lang.String) + * @see java.lang.System#getProperties() + */ + public static String getProperty(String key, String def) { + checkKey(key); +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPropertyAccess(key); +// } +// + return getProps().getProperty(key, def); + } + + /** + * Sets the system property indicated by the specified key. + *

+ * First, if a security manager exists, its + * SecurityManager.checkPermission method is called with a + * PropertyPermission(key, "write") permission. This may result in + * a SecurityException being thrown. If no exception is thrown, the specified + * property is set to the given value. + *

+ * + * @param key the name of the system property. + * @param value the value of the system property. + * @return the previous value of the system property, or null if it + * did not have one. + * + * @exception SecurityException if a security manager exists and its + * checkPermission method + * doesn't allow setting of the specified + * property. + * @exception NullPointerException if key or value + * is null. + * @exception IllegalArgumentException if key is empty. + * @see #getProperty + * @see java.lang.System#getProperty(java.lang.String) + * @see java.lang.System#getProperty(java.lang.String, java.lang.String) + * @see java.util.PropertyPermission + * @see SecurityManager#checkPermission + * @since 1.2 + */ + public static String setProperty(String key, String value) { + checkKey(key); +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPermission(new PropertyPermission(key, SecurityConstants.PROPERTY_WRITE_ACTION)); +// } +// + return (String) getProps().setProperty(key, value); + } + + /** + * Removes the system property indicated by the specified key. + *

+ * First, if a security manager exists, its + * SecurityManager.checkPermission method is called with a + * PropertyPermission(key, "write") permission. This may result in + * a SecurityException being thrown. If no exception is thrown, the specified + * property is removed. + *

+ * + * @param key the name of the system property to be removed. + * @return the previous string value of the system property, or + * null if there was no property with that key. + * + * @exception SecurityException if a security manager exists and its + * checkPropertyAccess method + * doesn't allow access to the specified + * system property. + * @exception NullPointerException if key is null. + * @exception IllegalArgumentException if key is empty. + * @see #getProperty + * @see #setProperty + * @see java.util.Properties + * @see java.lang.SecurityException + * @see java.lang.SecurityManager#checkPropertiesAccess() + * @since 1.5 + */ + public static String clearProperty(String key) { + checkKey(key); +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPermission(new PropertyPermission(key, "write")); +// } +// + return (String) getProps().remove(key); + } + + private static void checkKey(String key) { + if (key == null) { + throw new NullPointerException("key can't be null"); + } + if (key.equals("")) { + throw new IllegalArgumentException("key can't be empty"); + } + } + + /** + * Gets the value of the specified environment variable. An environment variable + * is a system-dependent external named value. + * + *

+ * If a security manager exists, its {@link SecurityManager#checkPermission + * checkPermission} method is called with a + * {@link RuntimePermission}("getenv."+name) permission. This may + * result in a {@link SecurityException} being thrown. If no exception is thrown + * the value of the variable name is returned. + * + *

+ * System properties and + * environment variables are both conceptually mappings between names + * and values. Both mechanisms can be used to pass user-defined information to a + * Java process. Environment variables have a more global effect, because they + * are visible to all descendants of the process which defines them, not just + * the immediate Java subprocess. They can have subtly different semantics, such + * as case insensitivity, on different operating systems. For these reasons, + * environment variables are more likely to have unintended side effects. It is + * best to use system properties where possible. Environment variables should be + * used when a global effect is desired, or when an external system interface + * requires an environment variable (such as PATH). + * + *

+ * On UNIX systems the alphabetic case of name is typically + * significant, while on Microsoft Windows systems it is typically not. For + * example, the expression + * System.getenv("FOO").equals(System.getenv("foo")) is likely to + * be true on Microsoft Windows. + * + * @param name the name of the environment variable + * @return the string value of the variable, or null if the + * variable is not defined in the system environment + * @throws NullPointerException if name is null + * @throws SecurityException if a security manager exists and its + * {@link SecurityManager#checkPermission + * checkPermission} method doesn't allow access to + * the environment variable name + * @see #getenv() + * @see ProcessBuilder#environment() + */ + public static String getenv(String name) { +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPermission(new RuntimePermission("getenv." + name)); +// } +// return ProcessEnvironment.getenv(name); + /** + * @j2sNative + * + * return Clazz._getenv(key); + */ + { + return null; + } + } + + /** + * Returns an unmodifiable string map view of the current system environment. + * The environment is a system-dependent mapping from names to values which is + * passed from parent to child processes. + * + *

+ * If the system does not support environment variables, an empty map is + * returned. + * + *

+ * The returned map will never contain null keys or values. Attempting to query + * the presence of a null key or value will throw a + * {@link NullPointerException}. Attempting to query the presence of a key or + * value which is not of type {@link String} will throw a + * {@link ClassCastException}. + * + *

+ * The returned map and its collection views may not obey the general contract + * of the {@link Object#equals} and {@link Object#hashCode} methods. + * + *

+ * The returned map is typically case-sensitive on all platforms. + * + *

+ * If a security manager exists, its {@link SecurityManager#checkPermission + * checkPermission} method is called with a + * {@link RuntimePermission}("getenv.*") permission. This may + * result in a {@link SecurityException} being thrown. + * + *

+ * When passing information to a Java subprocess, + * system properties are generally + * preferred over environment variables. + * + * @return the environment as a map of variable names to values + * @throws SecurityException if a security manager exists and its + * {@link SecurityManager#checkPermission + * checkPermission} method doesn't allow access to the + * process environment + * @see #getenv(String) + * @see ProcessBuilder#environment() + * @since 1.5 + */ + public static java.util.Map getenv() { + JSUtil.notImplemented(null); + return null; + +// SecurityManager sm = getSecurityManager(); +// if (sm != null) { +// sm.checkPermission(new RuntimePermission("getenv.*")); +// } +// return ProcessEnvironment.getenv(); + } + + /** + * Terminates the currently running Java Virtual Machine. The argument serves as + * a status code; by convention, a nonzero status code indicates abnormal + * termination. + *

+ * This method calls the exit method in class Runtime. + * This method never returns normally. + *

+ * The call System.exit(n) is effectively equivalent to the call: + *

+ * + *
+	 * Runtime.getRuntime().exit(n)
+	 * 
+ * + *
+ * + * @param status exit status. + * @throws SecurityException if a security manager exists and its + * checkExit method doesn't allow exit + * with the specified status. + * @see java.lang.Runtime#exit(int) + */ + public static void exit(int status) { + Runtime r = /** @j2sNative Clazz.load("java.lang.Runtime").getRuntime$() ||*/null; + r = Runtime.getRuntime(); + r.exit(status); + } + + /** + * Runs the garbage collector. + *

+ * Calling the gc method suggests that the Java Virtual Machine + * expend effort toward recycling unused objects in order to make the memory + * they currently occupy available for quick reuse. When control returns from + * the method call, the Java Virtual Machine has made a best effort to reclaim + * space from all discarded objects. + *

+ * The call System.gc() is effectively equivalent to the call: + *

+ * + *
+	 * Runtime.getRuntime().gc()
+	 * 
+ * + *
+ * + * @see java.lang.Runtime#gc() + */ + public static void gc() { + // Runtime.getRuntime().gc(); + } + + /** + * Runs the finalization methods of any objects pending finalization. + *

+ * Calling this method suggests that the Java Virtual Machine expend effort + * toward running the finalize methods of objects that have been + * found to be discarded but whose finalize methods have not yet + * been run. When control returns from the method call, the Java Virtual Machine + * has made a best effort to complete all outstanding finalizations. + *

+ * The call System.runFinalization() is effectively equivalent to + * the call:

+ * + *
+	 * Runtime.getRuntime().runFinalization()
+	 * 
+ * + *
+ * + * @see java.lang.Runtime#runFinalization() + */ + public static void runFinalization() { + //Runtime.getRuntime().runFinalization(); + } + + /** + * Enable or disable finalization on exit; doing so specifies that the + * finalizers of all objects that have finalizers that have not yet been + * automatically invoked are to be run before the Java runtime exits. By + * default, finalization on exit is disabled. + * + *

+ * If there is a security manager, its checkExit method is first + * called with 0 as its argument to ensure the exit is allowed. This could + * result in a SecurityException. + * + * @deprecated This method is inherently unsafe. It may result in finalizers + * being called on live objects while other threads are concurrently + * manipulating those objects, resulting in erratic behavior or + * deadlock. + * @param value indicating enabling or disabling of finalization + * @throws SecurityException if a security manager exists and its + * checkExit method doesn't allow the + * exit. + * + * @see java.lang.Runtime#exit(int) + * @see java.lang.Runtime#gc() + * @see java.lang.SecurityManager#checkExit(int) + * @since JDK1.1 + */ + @Deprecated + public static void runFinalizersOnExit(boolean value) { + //Runtime.runFinalizersOnExit(value); + } + + /** + * Loads the native library specified by the filename argument. The filename + * argument must be an absolute path name. + * + * If the filename argument, when stripped of any platform-specific library + * prefix, path, and file extension, indicates a library whose name is, for + * example, L, and a native library called L is statically linked with the VM, + * then the JNI_OnLoad_L function exported by the library is invoked rather than + * attempting to load a dynamic library. A filename matching the argument does + * not have to exist in the file system. See the JNI Specification for more + * details. + * + * Otherwise, the filename argument is mapped to a native library image in an + * implementation-dependent manner. + * + *

+ * The call System.load(name) is effectively equivalent to the + * call:

+ * + *
+	 * Runtime.getRuntime().load(name)
+	 * 
+ * + *
+ * + * @param filename the file to load. + * @exception SecurityException if a security manager exists and its + * checkLink method doesn't allow + * loading of the specified dynamic library + * @exception UnsatisfiedLinkError if either the filename is not an absolute + * path name, the native library is not + * statically linked with the VM, or the library + * cannot be mapped to a native library image by + * the host system. + * @exception NullPointerException if filename is null + * @see java.lang.Runtime#load(java.lang.String) + * @see java.lang.SecurityManager#checkLink(java.lang.String) + */ + public static void load(String filename) { + JSUtil.notImplemented(null); +// Runtime.getRuntime().load0(Reflection.getCallerClass(), filename); + } + + /** + * Loads the native library specified by the libname argument. The + * libname argument must not contain any platform specific prefix, + * file extension or path. If a native library called libname is + * statically linked with the VM, then the JNI_OnLoad_libname + * function exported by the library is invoked. See the JNI Specification for + * more details. + * + * Otherwise, the libname argument is loaded from a system library location and + * mapped to a native library image in an implementation- dependent manner. + *

+ * The call System.loadLibrary(name) is effectively equivalent to + * the call

+ * + *
+	 * Runtime.getRuntime().loadLibrary(name)
+	 * 
+ * + *
+ * + * @param libname the name of the library. + * @exception SecurityException if a security manager exists and its + * checkLink method doesn't allow + * loading of the specified dynamic library + * @exception UnsatisfiedLinkError if either the libname argument contains a + * file path, the native library is not + * statically linked with the VM, or the library + * cannot be mapped to a native library image by + * the host system. + * @exception NullPointerException if libname is null + * @see java.lang.Runtime#loadLibrary(java.lang.String) + * @see java.lang.SecurityManager#checkLink(java.lang.String) + */ + public static void loadLibrary(String libname) { + JSUtil.notImplemented(null); +// Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname); + } + + /** + * Maps a library name into a platform-specific string representing a native + * library. + * + * @param libname the name of the library. + * @return a platform-dependent native library name. + * @exception NullPointerException if libname is null + * @see java.lang.System#loadLibrary(java.lang.String) + * @see java.lang.ClassLoader#findLibrary(java.lang.String) + * @since 1.2 + */ + public static /* native */ String mapLibraryName(String libname) { + JSUtil.notImplemented(null); + return null; + } + +// /** +// * Create PrintStream for stdout/err based on encoding. +// */ +// private static PrintStream newPrintStream(FileOutputStream fos, String enc) { +// if (enc != null) { +// try { +// return new PrintStream(new BufferedOutputStream(fos, 128), true, enc); +// } catch (UnsupportedEncodingException uee) { +// } +// } +// return new PrintStream(new BufferedOutputStream(fos, 128), true); +// } +// +// /** +// * Initialize the system class. Called after thread initialization. +// */ +// private static void initializeSystemClass() { +// +// // VM might invoke JNU_NewStringPlatform() to set those encoding +// // sensitive properties (user.home, user.name, boot.class.path, etc.) +// // during "props" initialization, in which it may need access, via +// // System.getProperty(), to the related system encoding property that +// // have been initialized (put into "props") at early stage of the +// // initialization. So make sure the "props" is available at the +// // very beginning of the initialization and all system properties to +// // be put into it directly. +// props = new Properties(); +// initProperties(props); // initialized by the VM +// +// // There are certain system configurations that may be controlled by +// // VM options such as the maximum amount of direct memory and +// // Integer cache size used to support the object identity semantics +// // of autoboxing. Typically, the library will obtain these values +// // from the properties set by the VM. If the properties are for +// // internal implementation use only, these properties should be +// // removed from the system properties. +// // +// // See java.lang.Integer.IntegerCache and the +// // sun.misc.VM.saveAndRemoveProperties method for example. +// // +// // Save a private copy of the system properties object that +// // can only be accessed by the internal implementation. Remove +// // certain system properties that are not intended for public access. +// sun.misc.VM.saveAndRemoveProperties(props); +// +// +// lineSeparator = props.getProperty("line.separator"); +// sun.misc.Version.init(); +// +// FileInputStream fdIn = new FileInputStream(FileDescriptor.in); +// FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out); +// FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err); +// setIn0(new BufferedInputStream(fdIn)); +// setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))); +// setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding"))); +// +// // Load the zip library now in order to keep java.util.zip.ZipFile +// // from trying to use itself to load this library later. +// loadLibrary("zip"); +// +// // Setup Java signal handlers for HUP, TERM, and INT (where available). +// Terminator.setup(); +// +// // Initialize any miscellenous operating system settings that need to be +// // set for the class libraries. Currently this is no-op everywhere except +// // for Windows where the process-wide error mode is set before the java.io +// // classes are used. +// sun.misc.VM.initializeOSEnvironment(); +// +// // The main thread is not added to its thread group in the same +// // way as other threads; we must do it ourselves here. +// Thread current = Thread.currentThread(); +// current.getThreadGroup().add(current); +// +// // register shared secrets +// setJavaLangAccess(); +// +// // Subsystems that are invoked during initialization can invoke +// // sun.misc.VM.isBooted() in order to avoid doing things that should +// // wait until the application class loader has been set up. +// // IMPORTANT: Ensure that this remains the last initialization action! +// sun.misc.VM.booted(); +// } + +// private static void setJavaLangAccess() { +// // Allow privileged classes outside of java.lang +// sun.misc.SharedSecrets.setJavaLangAccess(new sun.misc.JavaLangAccess() { +// public sun.reflect.ConstantPool getConstantPool(Class klass) { +// return klass.getConstantPool(); +// } +// +// public boolean casAnnotationType(Class klass, AnnotationType oldType, AnnotationType newType) { +// return klass.casAnnotationType(oldType, newType); +// } +// +// public AnnotationType getAnnotationType(Class klass) { +// return klass.getAnnotationType(); +// } +// +// public Map, Annotation> getDeclaredAnnotationMap(Class klass) { +// return klass.getDeclaredAnnotationMap(); +// } +// +// public byte[] getRawClassAnnotations(Class klass) { +// return klass.getRawAnnotations(); +// } +// +// public byte[] getRawClassTypeAnnotations(Class klass) { +// return klass.getRawTypeAnnotations(); +// } +// +// public byte[] getRawExecutableTypeAnnotations(Executable executable) { +// return Class.getExecutableTypeAnnotationBytes(executable); +// } +// +// public > E[] getEnumConstantsShared(Class klass) { +// return klass.getEnumConstantsShared(); +// } +// +// public void blockedOn(Thread t, Interruptible b) { +// t.blockedOn(b); +// } +// +// public void registerShutdownHook(int slot, boolean registerShutdownInProgress, Runnable hook) { +// Shutdown.add(slot, registerShutdownInProgress, hook); +// } +// +// public int getStackTraceDepth(Throwable t) { +// return t.getStackTraceDepth(); +// } +// +// public StackTraceElement getStackTraceElement(Throwable t, int i) { +// return t.getStackTraceElement(i); +// } +// +// public String newStringUnsafe(char[] chars) { +// return new String(chars, true); +// } +// +// public Thread newThreadWithAcc(Runnable target, AccessControlContext acc) { +// return new Thread(target, acc); +// } +// +// public void invokeFinalize(Object o) throws Throwable { +// o.finalize(); +// } +// }); +// } +} diff --git a/sources/net.sf.j2s.java.core/src/java/lang/Thread.java b/sources/net.sf.j2s.java.core/src/java/lang/Thread.java index c88172255..cd6d8080c 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/Thread.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/Thread.java @@ -1466,7 +1466,7 @@ public ClassLoader getContextClassLoader() { /** * @j2sNative * - * cl.baseClass = {getName$:function(){return "swingjs"}}; + * cl.baseClass = {getName$:function(){return ""}}; */ return cl; } diff --git a/sources/net.sf.j2s.java.core/src/java/lang/reflect/AccessibleObject.java b/sources/net.sf.j2s.java.core/src/java/lang/reflect/AccessibleObject.java index 2bdf46276..55de7a7b3 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/reflect/AccessibleObject.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/reflect/AccessibleObject.java @@ -35,7 +35,8 @@ * @see ReflectPermission * @since 1.2 */ -public class AccessibleObject implements AnnotatedElement { +public abstract class AccessibleObject implements AnnotatedElement { + // BH SwingJS made abstract to let Field, Method, and Constructor handle AnnotatedElement interface by themselves static final Object[] emptyArgs = new Object[0]; /** @@ -46,6 +47,8 @@ protected AccessibleObject() { super(); } + boolean accessible = true; + /** * Returns the value of the accessible flag. This is false if access checks * are performed, true if they are skipped. @@ -53,7 +56,7 @@ protected AccessibleObject() { * @return the value of the accessible flag */ public boolean isAccessible() { - return false; + return accessible; } /** @@ -90,25 +93,77 @@ public static void setAccessible(AccessibleObject[] objects, boolean flag) * if the request is denied */ public void setAccessible(boolean flag) throws SecurityException { - return; + accessible = flag; } - public boolean isAnnotationPresent(Class annotationType) { - return false; - } - - public Annotation[] getDeclaredAnnotations() { - return new Annotation[0]; +// /** +// * @throws NullPointerException {@inheritDoc} +// * @since 1.5 +// */ +// public T getAnnotation(Class annotationClass) { +// throw new AssertionError("All subclasses should override this method"); +// } +// +// /** +// * {@inheritDoc} +// * @throws NullPointerException {@inheritDoc} +// * @since 1.5 +// */ +// @Override +// public boolean isAnnotationPresent(Class annotationClass) { +// return AnnotatedElement.super.isAnnotationPresent(annotationClass); +// } + +// /** +// * @throws NullPointerException {@inheritDoc} +// * @since 1.8 +// */ +// @Override +// public T[] getAnnotationsByType(Class annotationClass) { +// // superclass version is only for Class +// throw new AssertionError("All subclasses should override this method"); +// } +// + /** + * @since 1.5 + */ + @Override + public Annotation[] getAnnotations() { + return getDeclaredAnnotations(); } - - public Annotation[] getAnnotations() { - return new Annotation[0]; + + /** + * @throws NullPointerException {@inheritDoc} + * @since 1.8 + */ + //@Override + public T getDeclaredAnnotation(Class annotationClass) { + // Only annotations on classes are inherited, for all other + // objects getDeclaredAnnotation is the same as + // getAnnotation. + return getAnnotation(annotationClass); } - - public T getAnnotation(Class annotationType) { - return null; + + /** + * @throws NullPointerException {@inheritDoc} + * @since 1.8 + */ + @Override + public T[] getDeclaredAnnotationsByType(Class annotationClass) { + // Only annotations on classes are inherited, for all other + // objects getDeclaredAnnotationsByType is the same as + // getAnnotationsByType. + return getAnnotationsByType(annotationClass); } +// /** +// * @since 1.5 +// */ +// public Annotation[] getDeclaredAnnotations() { +// throw new AssertionError("All subclasses should override this method"); +// } +// +// static Object[] marshallArguments(@SuppressWarnings("rawtypes") Class[] parameterTypes, Object[] args) throws IllegalArgumentException { return null; @@ -144,22 +199,38 @@ float invokeF(Object receiver, Object args[]) return 0.0D; } - /*native*/ Class[] getParameterTypesImpl() {return null;} - - /*native*/ int getModifiers() {return Member.PUBLIC;} - - /*native*/ Class[] getExceptionTypesImpl(){return null;} - - /*native*/ String getSignature() {return "__FIELD__";} - - /*native*/ boolean checkAccessibility(Class senderClass, Object receiver) {return true;} + + @Override + public boolean isAnnotationPresent(Class annotationClass) { + return getAnnotation(annotationClass) != null; + } - static /*native*/ void initializeClass(Class clazz) {} + // SwingJS not implemented: + +// /*native*/ Class[] getParameterTypesImpl() {return null;} +// +// /*native*/ int getModifiers() {return Member.PUBLIC;} +// +// /*native*/ Class[] getExceptionTypesImpl(){return null;} +// +// /*native*/ String getSignature() {return "__FIELD__";} +// +// /*native*/ boolean checkAccessibility(Class senderClass, Object receiver) {return true;} +// +// static /*native*/ void initializeClass(Class clazz) {} +// /** * Answer the class at depth. Notes: 1) This method operates on the defining * classes of methods on stack. NOT the classes of receivers. 2) The item at * index zero describes the caller of this method. */ - static final /*native*/ Class getStackClass(int depth) {return null;}; + static final /*native*/ Class getStackClass(int depth) {return null;} + + public boolean isSynthetic() { + // TODO Auto-generated method stub + return false; + }; + + } diff --git a/sources/net.sf.j2s.java.core/src/java/lang/reflect/AnnotatedElement.java b/sources/net.sf.j2s.java.core/src/java/lang/reflect/AnnotatedElement.java index db276f1bf..402f8b723 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/reflect/AnnotatedElement.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/reflect/AnnotatedElement.java @@ -27,13 +27,12 @@ import java.lang.annotation.Annotation; import java.lang.annotation.AnnotationFormatError; -import java.lang.annotation.Repeatable; import java.util.Arrays; import java.util.LinkedHashMap; -import java.util.Map; import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; + import sun.reflect.annotation.AnnotationSupport; import sun.reflect.annotation.AnnotationType; @@ -254,9 +253,9 @@ public interface AnnotatedElement { * @throws NullPointerException if the given annotation class is null * @since 1.5 */ -// default boolean isAnnotationPresent(Class annotationClass) { -// return getAnnotation(annotationClass) != null; -// } + default boolean isAnnotationPresent(Class annotationClass) { + return getAnnotation(annotationClass) != null; + } /** * Returns this element's annotation for the specified type if @@ -321,7 +320,10 @@ public interface AnnotatedElement { * @throws NullPointerException if the given annotation class is null * @since 1.8 */ -// default T[] getAnnotationsByType(Class annotationClass) { +// SwingJS placed directly in Class; Field, Method, and Constructor do not implement this due to AccessibleObject override +//default + T[] getAnnotationsByType(Class annotationClass); +// { // /* // * Definition of associated: directly or indirectly present OR // * neither directly nor indirectly present AND the element is @@ -330,7 +332,7 @@ public interface AnnotatedElement { // * element. // */ // T[] result = getDeclaredAnnotationsByType(annotationClass); -// +//SwingJS this next was only for Class // if (result.length == 0 && // Neither directly nor indirectly present // ((Object)this) instanceof Class && // the element is a class // AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable @@ -344,30 +346,33 @@ public interface AnnotatedElement { // // return result; // } - - /** - * Returns this element's annotation for the specified type if - * such an annotation is directly present, else null. - * - * This method ignores inherited annotations. (Returns null if no - * annotations are directly present on this element.) - * - * @implSpec The default implementation first performs a null check - * and then loops over the results of {@link - * #getDeclaredAnnotations} returning the first annotation whose - * annotation type matches the argument type. - * - * @param the type of the annotation to query for and return if directly present - * @param annotationClass the Class object corresponding to the - * annotation type - * @return this element's annotation for the specified annotation type if - * directly present on this element, else null - * @throws NullPointerException if the given annotation class is null - * @since 1.8 - */ +// SwingJS placed directly in Class; Field, Method, and Constructor do not implement this due to AccessibleObject override +// /** +// * Returns this element's annotation for the specified type if +// * such an annotation is directly present, else null. +// * +// * This method ignores inherited annotations. (Returns null if no +// * annotations are directly present on this element.) +// * +// * @implSpec The default implementation first performs a null check +// * and then loops over the results of {@link +// * #getDeclaredAnnotations} returning the first annotation whose +// * annotation type matches the argument type. +// * +// * @param the type of the annotation to query for and return if directly present +// * @param annotationClass the Class object corresponding to the +// * annotation type +// * @return this element's annotation for the specified annotation type if +// * directly present on this element, else null +// * @throws NullPointerException if the given annotation class is null +// * @since 1.8 +// */ // default T getDeclaredAnnotation(Class annotationClass) { // Objects.requireNonNull(annotationClass); -// // Loop over all directly-present annotations looking for a matching one +// // Only annotations on classes are inherited, for all other +// // objects getDeclaredAnnotation is the same as +// // getAnnotation. +// // Loop over all directly-present annotations looking for a matching one // for (Annotation annotation : getDeclaredAnnotations()) { // if (annotationClass.equals(annotation.annotationType())) { // // More robust to do a dynamic cast at runtime instead @@ -422,8 +427,10 @@ public interface AnnotatedElement { * @throws NullPointerException if the given annotation class is null * @since 1.8 */ -// default T[] getDeclaredAnnotationsByType(Class annotationClass) { -// return null; + // SwingJS implemented directly in Class; already overridded in Field, Constructor, and Method + //default + T[] getDeclaredAnnotationsByType(Class annotationClass); +// { // Objects.requireNonNull(annotationClass); // return AnnotationSupport. // getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()). @@ -434,6 +441,7 @@ public interface AnnotatedElement { // annotationClass); // } + /** * Returns annotations that are directly present on this element. * This method ignores inherited annotations. @@ -448,4 +456,5 @@ public interface AnnotatedElement { * @since 1.5 */ Annotation[] getDeclaredAnnotations(); + } diff --git a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Constructor.java b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Constructor.java index 1ef59ab0e..3029df290 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Constructor.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Constructor.java @@ -16,6 +16,9 @@ package java.lang.reflect; import java.lang.annotation.Annotation; +import java.util.Map; + +import sun.reflect.annotation.AnnotationParser; /** * This class must be implemented by the VM vendor. This class models a @@ -23,13 +26,15 @@ * constructor can be invoked dynamically. * */ -public final class Constructor extends AccessibleObject implements GenericDeclaration, Member { +public final class Constructor extends AccessibleObject implements +//GenericDeclaration, +Member { private Class Class_; private Class[] parameterTypes; private Class[] exceptionTypes; private int modifiers; - private Object signature; + private String signature; private Object constr; /** @@ -37,10 +42,10 @@ public final class Constructor extends AccessibleObject implements GenericDec * these objects in Java code from the java.lang package via * sun.reflect.LangReflectAccess. */ - public Constructor(Class declaringClass, Class[] parameterTypes, Class[] checkedExceptions, + public Constructor(Class declaringClass, Class[] paramTypes, Class[] checkedExceptions, int modifiers) { this.Class_ = declaringClass; - this.parameterTypes = parameterTypes; + this.parameterTypes = paramTypes; this.exceptionTypes = checkedExceptions; this.modifiers = modifiers; // NO!! wrong: all of the SwingJS primitive classes run without parameterization @@ -48,9 +53,9 @@ public Constructor(Class declaringClass, Class[] parameterTypes, Class[ //parameterTypes = null; // Special case - constructors with parameters have c$$, not just c$. For whatever reason! // This signals NOT to add "$" if there are no parameters. - if (parameterTypes == null) - parameterTypes = Class.NO_PARAMETERS; - this.signature = "c$" + Class.argumentTypesToString(parameterTypes); + if (paramTypes == null) + paramTypes = Class.NO_PARAMETERS; + this.signature = "c$" + Class.argumentTypesToString(paramTypes); constr = /** @j2sNative this.Class_.$clazz$[this.signature] || */ null; } @@ -101,7 +106,13 @@ public T newInstance(Object... args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (this.constr != null) { Object[] a = Class.getArgumentArray(parameterTypes, args, false); - T instance = /** @j2sNative Clazz.new_(this.constr, a) || */ null; + T instance; + Class component = Class_.getComponentType(); + if (component != null) { + instance = /** @j2sNative Clazz.array(component, [a[0].intValue()]) || */ null; + } else { + instance = /** @j2sNative Clazz.new_(this.constr, a) || */ null; + } if (instance != null) return instance; } @@ -109,7 +120,10 @@ public T newInstance(Object... args) throw new IllegalArgumentException(message); } - public TypeVariable>[] getTypeParameters() { +// was GenericDeclaration @Override + public Object +// TypeVariable>[] + getTypeParameters() { return null; } @@ -179,6 +193,7 @@ public Type[] getGenericExceptionTypes() { * @since 1.5 */ public Annotation[][] getParameterAnnotations() { + // TODO return null; } @@ -195,6 +210,7 @@ public boolean isVarArgs() { return false; } + @Override public boolean isSynthetic() { return false; } @@ -209,6 +225,7 @@ public boolean isSynthetic() { * otherwise * @see #hashCode */ + @Override public boolean equals(Object object) { if (object != null && object instanceof Constructor) { Constructor other = (Constructor) object; @@ -234,6 +251,7 @@ public boolean equals(Object object) { * * @return the declaring class */ + @Override public Class getDeclaringClass() { return Class_; } @@ -256,6 +274,7 @@ public Class[] getExceptionTypes() { * @return the modifiers * @see java.lang.reflect.Modifier */ + @Override public int getModifiers() { return modifiers; } @@ -266,6 +285,7 @@ public int getModifiers() { * * @return the name */ + @Override public String getName() { return getDeclaringClass().getName(); } @@ -289,6 +309,7 @@ public Class[] getParameterTypes() { * @return the receiver's hash * @see #equals */ + @Override public int hashCode() { return getDeclaringClass().getName().hashCode(); } @@ -302,8 +323,45 @@ public int hashCode() { * * @return a printable representation for the receiver */ + @Override public String toString() { // TODO - just the simple JavaScript equivalent return Class_.getName() + "." + signature; } + + /** + * {@inheritDoc} + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return AnnotationParser.toArray(declaredAnnotations()); + } + + private transient Map, Annotation> declaredAnnotations; + + private synchronized Map, Annotation> declaredAnnotations() { + if (declaredAnnotations == null) { + declaredAnnotations = AnnotationParser.parseAnnotations(signature, getDeclaringClass(), true); + } + return declaredAnnotations; + } + + @SuppressWarnings("hiding") + @Override + public T getAnnotation(Class annotationClass) { + return (T) declaredAnnotations().get(annotationClass); + } + + // from AnnotatedElement + @SuppressWarnings("hiding") + @Override + public T[] getAnnotationsByType(Class annotationClass) { + return getDeclaredAnnotationsByType(annotationClass); + } + + public String getSignature() { + return (String) signature; + } + + } diff --git a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Field.java b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Field.java index b8d338319..b7b08cfd1 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Field.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Field.java @@ -15,6 +15,12 @@ package java.lang.reflect; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Map; + +import sun.reflect.annotation.AnnotationParser; + /** * This class must be implemented by the VM vendor. This class models a field. * Information about the field can be accessed, and the field's value can be @@ -30,7 +36,7 @@ public final class Field extends AccessibleObject implements Member { private Class Class_; private int modifiers; boolean isStatic; - + public Field(Class cl, String name, int modifiers) { jsName = name; Class_ = cl; @@ -38,29 +44,6 @@ public Field(Class cl, String name, int modifiers) { isStatic = (modifiers == Modifier.STATIC); } - @Override - public boolean isAccessible() { - // SwingJS - return true; - } - - public boolean isSynthetic() { - return false; - } - - /** - *

- * Returns the String representation of the field's declaration, including the - * type parameters. - *

- * - * @return An instance of String. - * @since 1.5 - */ - public String toGenericString() { - return null; - } - /** *

* Indicates whether or not this field is an enumeration constant. @@ -101,16 +84,17 @@ public Type getGenericType() { * @return true if the specified object is equal to this Field, false otherwise * @see #hashCode */ + @Override public boolean equals(Object object) { if (object == null) return false; Object o = getObj(object); -; + ; /** * @j2sNative * - * o = o[this.jsName]; - * if (typeof o == "number" || typeof o == "boolean") return false; + * o = o[this.jsName]; if (typeof o == "number" || typeof o == + * "boolean") return false; */ return object.equals(o); } @@ -143,14 +127,14 @@ public Object get(Object object) throws IllegalAccessException, IllegalArgumentE object = getObj(object); /** - * @j2sNative + * @j2sNative * - * object = object[this.jsName]; - * if (typeof object == "number") return (object == object|0 ? new Integer(object) : new Double(object)); - * if (typeof object == "boolean") return new Boolean(object); + * object = object[this.jsName]; if (typeof object == "number") + * return (object == object|0 ? new Integer(object) : new + * Double(object)); if (typeof object == "boolean") return new + * Boolean(object); * - * if (typeof object != "undefined") - * return object; + * if (typeof object != "undefined") return object; */ throw new NullPointerException(); } @@ -159,7 +143,8 @@ Object getObj(Object object) { /** * @j2sNative * - * return (object == null || this.isStatic ? this.Class_.$clazz$ : object); + * return (object == null || this.isStatic ? this.Class_.$clazz$ : + * object); */ { return null; @@ -268,7 +253,6 @@ public char getChar(Object object) throws IllegalAccessException, IllegalArgumen return 0; } - } /** @@ -276,6 +260,7 @@ public char getChar(Object object) throws IllegalAccessException, IllegalArgumen * * @return the declaring class */ + @Override public Class getDeclaringClass() { return Class_; } @@ -284,7 +269,7 @@ public Class getDeclaringClass() { * Return the value of the field in the specified object as a double. This * reproduces the effect of object.fieldName *

- * If the modelled field is static, the object argument is ignored. Otherwise, + * If the modeled field is static, the object argument is ignored. Otherwise, * if the object is null, a NullPointerException is thrown. If the object is not * an instance of the declaring class of the method, an IllegalArgumentException * is thrown. @@ -427,13 +412,17 @@ public long getLong(Object object) throws IllegalAccessException, IllegalArgumen * @return the modifiers * @see java.lang.reflect.Modifier */ - public int getModifiers() {return modifiers;} + @Override + public int getModifiers() { + return modifiers; + } /** * Return the name of the modelled field. * * @return the name */ + @Override public String getName() { return jsName; } @@ -474,19 +463,25 @@ public short getShort(Object object) throws IllegalAccessException, IllegalArgum } +// @Override String getSignature() { return jsName; } + private Object myType; + /** * Return the {@link Class} associated with the type of this field. * * @return the type */ public Class getType() { - return null; + if (myType == null || myType instanceof Class) + return (Class) myType; + return (Class) (myType = AnnotationParser.JSAnnotationObject.typeForString(myType.toString(), false)); } + /** * Answers an integer hash code for the receiver. Objects which are equal answer * the same value for this method. @@ -496,6 +491,7 @@ public Class getType() { * @return the receiver's hash * @see #equals */ + @Override public int hashCode() { return 0; } @@ -534,7 +530,7 @@ public void set(Object object, Object value) throws IllegalAccessException, Ille * object[this.jsName] = value; * */ - + } /** @@ -827,7 +823,51 @@ public void setShort(Object object, short value) throws IllegalAccessException, * * @return a printable representation for the receiver */ + @Override public String toString() { return "[field: " + jsName + "]"; } + + /** + * {@inheritDoc} + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return AnnotationParser.toArray(declaredAnnotations()); + } + + private transient Map, Annotation> declaredAnnotations; + + private synchronized Map, Annotation> declaredAnnotations() { + if (declaredAnnotations == null) { +// +// Field root = null;//??? this.root; +// if (root != null) { +// declaredAnnotations = root.declaredAnnotations(); +// } else + { + declaredAnnotations = AnnotationParser.parseAnnotations(getName(), getDeclaringClass(), false); + } + } + return declaredAnnotations; + } + +// private native byte[] getTypeAnnotationBytes0(); + + + @Override + public T getAnnotation(Class annotationClass) { + return (T) declaredAnnotations().get(annotationClass); + } + + // from AnnotatedElement + @Override + public T[] getAnnotationsByType(Class annotationClass) { + return getDeclaredAnnotationsByType(annotationClass); + } + + public void _setTypeString(String type) { + myType = type; + } + } diff --git a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Method.java b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Method.java index 51863297b..85d41dc54 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Method.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Method.java @@ -16,6 +16,10 @@ package java.lang.reflect; import java.lang.annotation.Annotation; +import java.util.Map; + +import javajs.api.JSFunction; +import sun.reflect.annotation.AnnotationParser; /** * This class must be implemented by the VM vendor. This class models a method. @@ -28,6 +32,10 @@ public final class Method extends AccessibleObject implements GenericDeclaration private String signature; private Class Class_; + + private JSFunction $meth$; // SwingJS -- set in AnnotationParser + boolean isAnnotation; // SwingJS -- set in AnnotationParser + // This is guaranteed to be interned by the VM in the 1.4 // reflection implementation private String name; @@ -37,23 +45,30 @@ public final class Method extends AccessibleObject implements GenericDeclaration private int modifiers = Member.PUBLIC; boolean isProxy; +// private Annotation[] annotations; +// private Annotation[] parameterAnnotations; + private Object annotationDefault; + /** * Package-private constructor used by ReflectAccess to enable instantiation of * these objects in Java code from the java.lang package via * sun.reflect.LangReflectAccess. */ - public Method(Class declaringClass, String name, Class[] parameterTypes, Class returnType, + public Method(Class declaringClass, String name, Class[] paramTypes, Class returnType, Class[] checkedExceptions, int modifiers) { this.Class_ = declaringClass; this.name = name; - this.parameterTypes = (parameterTypes == null ? Class.NO_PARAMETERS : parameterTypes); + this.parameterTypes = (paramTypes == null ? Class.NO_PARAMETERS : paramTypes); this.returnType = returnType; this.exceptionTypes = checkedExceptions; this.modifiers = modifiers; // modifier PUBLIC means this is from Class.java getMethods - if (parameterTypes != null && parameterTypes.length == 0) - parameterTypes = null; - this.signature = (declaringClass.$methodList$ == null ? name + Class.argumentTypesToString(parameterTypes) : name); + if (paramTypes != null && paramTypes.length == 0) + paramTypes = null; + this.signature = (paramTypes == Class.UNKNOWN_PARAMETERS + || declaringClass.$methodList$ != null + || declaringClass.isAnnotation() + ? name : name + Class.argumentTypesToString(paramTypes)); } /** @@ -112,14 +127,16 @@ public Object invoke(Object receiver, Object... args) // proxy does uses this.Class_ as its receiver and ensures initialization boolean isProxy = this.isProxy; Object[] a = Class.getArgumentArray(parameterTypes, args, isProxy); - Object c = (isProxy ? receiver : this.Class_); - Object m = null, val = null; + Object c = (isProxy || isAnnotation ? receiver : Class_); + Object m = null//$meth$ + , val = null; + String sig = signature; /** * @j2sNative * - * if (!isProxy) {c = c.$clazz$;} + * !isProxy && c.$clazz$ && (c = c.$clazz$); * Clazz._initClass(c,1,1,0); - * m= c[this.signature] || c.prototype && c.prototype[this.signature]; + * m || (m = c[sig]) || (m = this.$meth$) || (m = c.prototype && c.prototype[sig]); * val = (m == null ? null : m.apply(receiver,a)); */ @@ -128,7 +145,7 @@ public Object invoke(Object receiver, Object... args) + "." + signature + " was not found"; throw new IllegalArgumentException(message); } - return (val == null || isProxy ? val : wrap(val)); + return (val == null || isProxy || isAnnotation ? val : wrap(val)); } Object wrap(Object o) { @@ -142,6 +159,7 @@ Object wrap(Object o) { return o; } + @Override public TypeVariable[] getTypeParameters() { return null; } @@ -231,6 +249,7 @@ public Type getGenericReturnType() { * @since 1.5 */ public Annotation[][] getParameterAnnotations() { + // TODO return null; } @@ -260,9 +279,10 @@ public boolean isBridge() { return false; } + @Override public boolean isSynthetic() { - return false; - } + return super.isSynthetic(); + } /** *

@@ -275,7 +295,8 @@ public boolean isSynthetic() { * @since 1.5 */ public Object getDefaultValue() { - return null; + // just the value in SwingJS + return annotationDefault; } /** @@ -287,6 +308,7 @@ public Object getDefaultValue() { * @return true if the specified object is equal to this Method, false otherwise * @see #hashCode */ + @Override public boolean equals(Object object) { if (object != null && object instanceof Method) { Method other = (Method) object; @@ -311,6 +333,7 @@ public boolean equals(Object object) { * * @return the declaring class */ + @Override public Class getDeclaringClass() { return Class_; } @@ -333,6 +356,7 @@ public Class[] getExceptionTypes() { * @return the modifiers * @see java.lang.reflect.Modifier */ + @Override public int getModifiers() { return modifiers; } @@ -342,6 +366,7 @@ public int getModifiers() { * * @return the name */ + @Override public String getName() { return name; } @@ -354,7 +379,8 @@ public String getName() { * @return the parameter types */ public Class[] getParameterTypes() { - return parameterTypes; + return (parameterTypes == Class.UNKNOWN_PARAMETERS ? + parameterTypes = AnnotationParser.JSAnnotationObject.guessMethodParameterTypes(signature) : parameterTypes); } /** @@ -363,11 +389,14 @@ public Class[] getParameterTypes() { * @return the return type */ public Class getReturnType() { - return returnType; + // SwingJS will store this as a String to avoid unnecessary class loading + if (returnType == null || returnType instanceof Class) + return (Class) returnType; + return (Class) (returnType = AnnotationParser.JSAnnotationObject.typeForString(returnType.toString(), false)); } - @Override +// @Override public String getSignature() { return (String) signature; } @@ -380,6 +409,7 @@ public String getSignature() { * @return the receiver's hash * @see #equals */ + @Override public int hashCode() { return getDeclaringClass().getName().hashCode() ^ getName().hashCode(); } @@ -394,8 +424,51 @@ public int hashCode() { * * @return a printable representation for the receiver */ + @Override public String toString() { return Class_.getName() + "." + name; } + /** + * {@inheritDoc} + */ + @Override + public Annotation[] getDeclaredAnnotations() { + return AnnotationParser.toArray(declaredAnnotations()); + } + + private transient Map, Annotation> declaredAnnotations; + + private synchronized Map, Annotation> declaredAnnotations() { + if (declaredAnnotations == null) { + declaredAnnotations = AnnotationParser.parseAnnotations(signature, getDeclaringClass(), true); + } + return declaredAnnotations; + } + + public void _setJSMethod(Object o) { + $meth$ = (JSFunction) o; + signature = /** @j2sNative o && o.exName || */ null; + } + + public Object _getJSMethod() { + return $meth$; + } + + public void setDefaultValue(Object val) { + annotationDefault = val; + } + + + @Override + public T getAnnotation(Class annotationClass) { + return (T) declaredAnnotations().get(annotationClass); + } + + // from AnnotatedElement + @Override + public T[] getAnnotationsByType(Class annotationClass) { + return getDeclaredAnnotationsByType(annotationClass); + } + } diff --git a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Modifier.java b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Modifier.java index ffd40d2a0..13df0f181 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Modifier.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Modifier.java @@ -57,11 +57,9 @@ public class Modifier { static final int VARARGS = 0x80; - static final int SYNTHETIC = 0x1000; - + static final int SYNTHETIC = 0x1000; static final int ANNOTATION = 0x2000; - - public static final int ENUM = 0x4000; + static final int ENUM = 0x4000; public Modifier() { } diff --git a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Proxy.java b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Proxy.java index 91b1922dd..702931967 100644 --- a/sources/net.sf.j2s.java.core/src/java/lang/reflect/Proxy.java +++ b/sources/net.sf.j2s.java.core/src/java/lang/reflect/Proxy.java @@ -658,9 +658,8 @@ private static Class defineClass0(ClassLoader loader, String name, Class[] * * var cl$ = cl.$clazz$; * - * cl$.$clinit$ = function() {Clazz.load(cl$, 1);}; + * cl$.$clinit$ = 1; * Clazz.newMeth(cl$, '$init$', function () {}, 1); - * Clazz.newMeth(cl$, '$init0$', function () {}, 1); * Clazz.newMeth(cl$, "c$$reflect_InvocationHandler", function(h) { * cl$.superclazz.c$$reflect_InvocationHandler.apply(this, [h]); * cl$.$init$.apply(this); @@ -672,7 +671,7 @@ private static Class defineClass0(ClassLoader loader, String name, Class[] for (int i = 0; i < interfaces.length; i++) { Method[] methods = interfaces[i].getDeclaredMethods(); for (int j = 0; j < methods.length; j++) { - setJSPrototype(cl, methods[j], false); + setJSPrototype(cl, methods[j], interfaces[i].isAnnotation()); } if (methods.length == 1) setJSPrototype(cl, methods[0], true); @@ -681,18 +680,19 @@ private static Class defineClass0(ClassLoader loader, String name, Class[] } @SuppressWarnings("unused") - private static void setJSPrototype(Class cl, Method m, boolean isFunctionalInterfaceMethod) { + private static void setJSPrototype(Class cl, Method m, boolean add$) { String mname = m.getName(); // SwingJS transfers the invocation to a temporary method, then invokes it /** * @j2sNative * - * if (isFunctionalInterfaceMethod) { mname = mname.split("$")[0] + "$"; } - * m.Class_ = cl; m.isProxy = true; cl.$clazz$.prototype[mname] = + * var mname1 = (add$ ? mname.split("$")[0] + "$" : mname); + * m.Class_ = cl; m.isProxy = true; + * cl.$clazz$.prototype[mname] = cl.$clazz$.prototype[mname1] = * function() { var args = new Array(arguments.length); for (var k = * arguments.length; --k >= 0;)args[k] = arguments[k]; - * this.h.invoke$O$reflect_Method$OA(this, m, args); } + * return(this.h.invoke$O$reflect_Method$OA(this, m, args)); } */ } } diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBuffer.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBuffer.java index 262435127..42998dd59 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/ByteBuffer.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBuffer.java @@ -25,8 +25,6 @@ package java.nio; -import java.util.Spliterator; - /** * $A$ byte buffer. * @@ -256,6 +254,11 @@ public abstract class ByteBuffer { public abstract CharBuffer asCharBuffer(); + public abstract ShortBuffer asShortBuffer(); + public abstract IntBuffer asIntBuffer(); + public abstract LongBuffer asLongBuffer(); + public abstract FloatBuffer asFloatBuffer(); + public abstract DoubleBuffer asDoubleBuffer(); // These fields are declared here rather than in Heap-X-Buffer in order to @@ -757,7 +760,8 @@ public final ByteBuffer put(byte[] src) { * @return true if, and only if, this buffer * is backed by an array and is not read-only */ - public final boolean hasArray() { + @Override + public final boolean hasArray() { return (hb != null) && !isReadOnly; } @@ -780,7 +784,8 @@ public final boolean hasArray() { * @throws UnsupportedOperationException * If this buffer is not backed by an accessible array */ - public final byte[] array() { + @Override + public final byte[] array() { if (hb == null) throw new UnsupportedOperationException(); if (isReadOnly) @@ -808,7 +813,8 @@ public final byte[] array() { * @throws UnsupportedOperationException * If this buffer is not backed by an accessible array */ - public final int arrayOffset() { + @Override + public final int arrayOffset() { if (hb == null) throw new UnsupportedOperationException(); if (isReadOnly) @@ -863,7 +869,8 @@ public final int arrayOffset() { * * @return true if, and only if, this buffer is direct */ - public abstract boolean isDirect(); + @Override + public abstract boolean isDirect(); /** @@ -871,7 +878,8 @@ public final int arrayOffset() { * * @return A summary string */ - public String toString() { + @Override + public String toString() { StringBuffer sb = new StringBuffer(); sb.append(getClass().getName()); sb.append("[pos="); @@ -901,7 +909,8 @@ public String toString() { * * @return The current hash code of this buffer */ - public int hashCode() { + @Override + public int hashCode() { int h = 1; int p = position(); for (int i = limit() - 1; i >= p; i--) @@ -941,7 +950,8 @@ public int hashCode() { * @return true if, and only if, this buffer is equal to the * given object */ - public boolean equals(Object ob) { + @Override + public boolean equals(Object ob) { if (this == ob) return true; if (!(ob instanceof ByteBuffer)) @@ -983,7 +993,8 @@ private static boolean equals(byte x, byte y) { * @return A negative integer, zero, or a positive integer as this buffer * is less than, equal to, or greater than the given buffer */ - public int compareTo(ByteBuffer that) { + @Override + public int compareTo(ByteBuffer that) { int n = this.position() + Math.min(this.remaining(), that.remaining()); for (int i = this.position(), j = that.position(); i < n; i++, j++) { int cmp = compare(this.get(i), that.get(j)); diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsCharBufferL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsCharBufferL.java new file mode 100644 index 000000000..293299034 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsCharBufferL.java @@ -0,0 +1,161 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsCharBufferL + extends CharBuffer +{ + protected final ByteBuffer bb; + protected final int offset; + + ByteBufferAsCharBufferL(ByteBuffer paramByteBuffer) + { + super(-1, 0, paramByteBuffer + .remaining() >> 1, paramByteBuffer + .remaining() >> 1); + this.bb = paramByteBuffer; + + int i = capacity(); + limit(i); + int j = position(); + assert (j <= i); + this.offset = j; + } + + ByteBufferAsCharBufferL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramInt1, paramInt2, paramInt3, paramInt4); + this.bb = paramByteBuffer; + this.offset = paramInt5; + } + + public CharBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 1) + this.offset; + assert (m >= 0); + return new ByteBufferAsCharBufferL(this.bb, -1, 0, k, k, m); + } + + public CharBuffer duplicate() + { + return new ByteBufferAsCharBufferL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public CharBuffer asReadOnlyBuffer() + { + return new ByteBufferAsCharBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + protected int ix(int paramInt) + { + return (paramInt << 1) + this.offset; + } + + public char get() + { + return Bits.getCharL(this.bb, ix(nextGetIndex())); + } + + public char get(int paramInt) + { + return Bits.getCharL(this.bb, ix(checkIndex(paramInt))); + } + + char getUnchecked(int paramInt) + { + return Bits.getCharL(this.bb, ix(paramInt)); + } + + public CharBuffer put(char paramChar) + { + Bits.putCharL(this.bb, ix(nextPutIndex()), paramChar); + return this; + } + + public CharBuffer put(int paramInt, char paramChar) + { + Bits.putCharL(this.bb, ix(checkIndex(paramInt)), paramChar); + return this; + } + + public CharBuffer compact() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + + ByteBuffer localByteBuffer1 = this.bb.duplicate(); + localByteBuffer1.limit(ix(j)); + localByteBuffer1.position(ix(0)); + ByteBuffer localByteBuffer2 = localByteBuffer1.slice(); + localByteBuffer2.position(i << 1); + localByteBuffer2.compact(); + position(k); + limit(capacity()); + discardMark(); + return this; + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return false; + } + + public String toString(int paramInt1, int paramInt2) + { + if ((paramInt2 > limit()) || (paramInt1 > paramInt2)) { + throw new IndexOutOfBoundsException(); + } + try + { + int i = paramInt2 - paramInt1; + char[] arrayOfChar = new char[i]; + CharBuffer localCharBuffer1 = CharBuffer.wrap(arrayOfChar); + CharBuffer localCharBuffer2 = duplicate(); + localCharBuffer2.position(paramInt1); + localCharBuffer2.limit(paramInt2); + localCharBuffer1.put(localCharBuffer2); + return new String(arrayOfChar); + } + catch (StringIndexOutOfBoundsException localStringIndexOutOfBoundsException) + { + throw new IndexOutOfBoundsException(); + } + } + + public CharBuffer subSequence(int paramInt1, int paramInt2) + { + int i = position(); + int j = limit(); + assert (i <= j); + i = i <= j ? i : j; + int k = j - i; + if ((paramInt1 < 0) || (paramInt2 > k) || (paramInt1 > paramInt2)) { + throw new IndexOutOfBoundsException(); + } + return new ByteBufferAsCharBufferL(this.bb, -1, i + paramInt1, i + paramInt2, + + capacity(), this.offset); + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsCharBufferRL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsCharBufferRL.java new file mode 100644 index 000000000..1c8d43a62 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsCharBufferRL.java @@ -0,0 +1,107 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsCharBufferRL + extends ByteBufferAsCharBufferL +{ + ByteBufferAsCharBufferRL(ByteBuffer paramByteBuffer) + { + super(paramByteBuffer); + } + + ByteBufferAsCharBufferRL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramByteBuffer, paramInt1, paramInt2, paramInt3, paramInt4, paramInt5); + } + + public CharBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 1) + this.offset; + assert (m >= 0); + return new ByteBufferAsCharBufferRL(this.bb, -1, 0, k, k, m); + } + + public CharBuffer duplicate() + { + return new ByteBufferAsCharBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public CharBuffer asReadOnlyBuffer() + { + return duplicate(); + } + + public CharBuffer put(char paramChar) + { + throw new ReadOnlyBufferException(); + } + + public CharBuffer put(int paramInt, char paramChar) + { + throw new ReadOnlyBufferException(); + } + + public CharBuffer compact() + { + throw new ReadOnlyBufferException(); + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return true; + } + + public String toString(int paramInt1, int paramInt2) + { + if ((paramInt2 > limit()) || (paramInt1 > paramInt2)) { + throw new IndexOutOfBoundsException(); + } + try + { + int i = paramInt2 - paramInt1; + char[] arrayOfChar = new char[i]; + CharBuffer localCharBuffer1 = CharBuffer.wrap(arrayOfChar); + CharBuffer localCharBuffer2 = duplicate(); + localCharBuffer2.position(paramInt1); + localCharBuffer2.limit(paramInt2); + localCharBuffer1.put(localCharBuffer2); + return new String(arrayOfChar); + } + catch (StringIndexOutOfBoundsException localStringIndexOutOfBoundsException) + { + throw new IndexOutOfBoundsException(); + } + } + + public CharBuffer subSequence(int paramInt1, int paramInt2) + { + int i = position(); + int j = limit(); + assert (i <= j); + i = i <= j ? i : j; + int k = j - i; + if ((paramInt1 < 0) || (paramInt2 > k) || (paramInt1 > paramInt2)) { + throw new IndexOutOfBoundsException(); + } + return new ByteBufferAsCharBufferRL(this.bb, -1, i + paramInt1, i + paramInt2, + + capacity(), this.offset); + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferB.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferB.java index 344fbcbe1..c3954bc11 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferB.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferB.java @@ -27,7 +27,8 @@ class ByteBufferAsDoubleBufferB this.offset = paramInt5; } - public DoubleBuffer slice() + @Override +public DoubleBuffer slice() { int i = position(); int j = limit(); @@ -38,7 +39,8 @@ public DoubleBuffer slice() return new ByteBufferAsDoubleBufferB(this.bb, -1, 0, k, k, m); } - public DoubleBuffer duplicate() + @Override +public DoubleBuffer duplicate() { return new ByteBufferAsDoubleBufferB(this.bb, markValue(), @@ -47,7 +49,8 @@ public DoubleBuffer duplicate() capacity(), this.offset); } - public DoubleBuffer asReadOnlyBuffer() + @Override +public DoubleBuffer asReadOnlyBuffer() { return new ByteBufferAsDoubleBufferRB(this.bb, markValue(), @@ -61,29 +64,34 @@ protected int ix(int paramInt) return (paramInt << 3) + this.offset; } - public double get() + @Override +public double get() { return Bits.getDoubleB(this.bb, ix(nextGetIndex())); } - public double get(int paramInt) + @Override +public double get(int paramInt) { return Bits.getDoubleB(this.bb, ix(checkIndex(paramInt))); } - public DoubleBuffer put(double paramDouble) + @Override +public DoubleBuffer put(double paramDouble) { Bits.putDoubleB(this.bb, ix(nextPutIndex()), paramDouble); return this; } - public DoubleBuffer put(int paramInt, double paramDouble) + @Override +public DoubleBuffer put(int paramInt, double paramDouble) { Bits.putDoubleB(this.bb, ix(checkIndex(paramInt)), paramDouble); return this; } - public DoubleBuffer compact() + @Override +public DoubleBuffer compact() { int i = position(); int j = limit(); @@ -102,17 +110,20 @@ public DoubleBuffer compact() return this; } - public boolean isDirect() + @Override +public boolean isDirect() { return this.bb.isDirect(); } - public boolean isReadOnly() + @Override +public boolean isReadOnly() { return false; } - public ByteOrder order() + @Override +public ByteOrder order() { return ByteOrder.BIG_ENDIAN; } diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferL.java new file mode 100644 index 000000000..2ad9a9756 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferL.java @@ -0,0 +1,119 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsDoubleBufferL + extends DoubleBuffer +{ + protected final ByteBuffer bb; + protected final int offset; + + ByteBufferAsDoubleBufferL(ByteBuffer paramByteBuffer) + { + super(-1, 0, paramByteBuffer + .remaining() >> 3, paramByteBuffer + .remaining() >> 3); + this.bb = paramByteBuffer; + + int i = capacity(); + limit(i); + int j = position(); + assert (j <= i); + this.offset = j; + } + + ByteBufferAsDoubleBufferL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramInt1, paramInt2, paramInt3, paramInt4); + this.bb = paramByteBuffer; + this.offset = paramInt5; + } + + public DoubleBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 3) + this.offset; + assert (m >= 0); + return new ByteBufferAsDoubleBufferL(this.bb, -1, 0, k, k, m); + } + + public DoubleBuffer duplicate() + { + return new ByteBufferAsDoubleBufferL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public DoubleBuffer asReadOnlyBuffer() + { + return new ByteBufferAsDoubleBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + protected int ix(int paramInt) + { + return (paramInt << 3) + this.offset; + } + + public double get() + { + return Bits.getDoubleL(this.bb, ix(nextGetIndex())); + } + + public double get(int paramInt) + { + return Bits.getDoubleL(this.bb, ix(checkIndex(paramInt))); + } + + public DoubleBuffer put(double paramDouble) + { + Bits.putDoubleL(this.bb, ix(nextPutIndex()), paramDouble); + return this; + } + + public DoubleBuffer put(int paramInt, double paramDouble) + { + Bits.putDoubleL(this.bb, ix(checkIndex(paramInt)), paramDouble); + return this; + } + + public DoubleBuffer compact() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + + ByteBuffer localByteBuffer1 = this.bb.duplicate(); + localByteBuffer1.limit(ix(j)); + localByteBuffer1.position(ix(0)); + ByteBuffer localByteBuffer2 = localByteBuffer1.slice(); + localByteBuffer2.position(i << 3); + localByteBuffer2.compact(); + position(k); + limit(capacity()); + discardMark(); + return this; + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return false; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferRL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferRL.java new file mode 100644 index 000000000..d644f459b --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsDoubleBufferRL.java @@ -0,0 +1,70 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsDoubleBufferRL + extends ByteBufferAsDoubleBufferL +{ + ByteBufferAsDoubleBufferRL(ByteBuffer paramByteBuffer) + { + super(paramByteBuffer); + } + + ByteBufferAsDoubleBufferRL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramByteBuffer, paramInt1, paramInt2, paramInt3, paramInt4, paramInt5); + } + + public DoubleBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 3) + this.offset; + assert (m >= 0); + return new ByteBufferAsDoubleBufferRL(this.bb, -1, 0, k, k, m); + } + + public DoubleBuffer duplicate() + { + return new ByteBufferAsDoubleBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public DoubleBuffer asReadOnlyBuffer() + { + return duplicate(); + } + + public DoubleBuffer put(double paramDouble) + { + throw new ReadOnlyBufferException(); + } + + public DoubleBuffer put(int paramInt, double paramDouble) + { + throw new ReadOnlyBufferException(); + } + + public DoubleBuffer compact() + { + throw new ReadOnlyBufferException(); + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return true; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsFloatBufferL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsFloatBufferL.java new file mode 100644 index 000000000..eeb96b634 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsFloatBufferL.java @@ -0,0 +1,119 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsFloatBufferL + extends FloatBuffer +{ + protected final ByteBuffer bb; + protected final int offset; + + ByteBufferAsFloatBufferL(ByteBuffer paramByteBuffer) + { + super(-1, 0, paramByteBuffer + .remaining() >> 2, paramByteBuffer + .remaining() >> 2); + this.bb = paramByteBuffer; + + int i = capacity(); + limit(i); + int j = position(); + assert (j <= i); + this.offset = j; + } + + ByteBufferAsFloatBufferL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramInt1, paramInt2, paramInt3, paramInt4); + this.bb = paramByteBuffer; + this.offset = paramInt5; + } + + public FloatBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 2) + this.offset; + assert (m >= 0); + return new ByteBufferAsFloatBufferL(this.bb, -1, 0, k, k, m); + } + + public FloatBuffer duplicate() + { + return new ByteBufferAsFloatBufferL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public FloatBuffer asReadOnlyBuffer() + { + return new ByteBufferAsFloatBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + protected int ix(int paramInt) + { + return (paramInt << 2) + this.offset; + } + + public float get() + { + return Bits.getFloatL(this.bb, ix(nextGetIndex())); + } + + public float get(int paramInt) + { + return Bits.getFloatL(this.bb, ix(checkIndex(paramInt))); + } + + public FloatBuffer put(float paramFloat) + { + Bits.putFloatL(this.bb, ix(nextPutIndex()), paramFloat); + return this; + } + + public FloatBuffer put(int paramInt, float paramFloat) + { + Bits.putFloatL(this.bb, ix(checkIndex(paramInt)), paramFloat); + return this; + } + + public FloatBuffer compact() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + + ByteBuffer localByteBuffer1 = this.bb.duplicate(); + localByteBuffer1.limit(ix(j)); + localByteBuffer1.position(ix(0)); + ByteBuffer localByteBuffer2 = localByteBuffer1.slice(); + localByteBuffer2.position(i << 2); + localByteBuffer2.compact(); + position(k); + limit(capacity()); + discardMark(); + return this; + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return false; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsFloatBufferRL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsFloatBufferRL.java new file mode 100644 index 000000000..7abf342e3 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsFloatBufferRL.java @@ -0,0 +1,70 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsFloatBufferRL + extends ByteBufferAsFloatBufferL +{ + ByteBufferAsFloatBufferRL(ByteBuffer paramByteBuffer) + { + super(paramByteBuffer); + } + + ByteBufferAsFloatBufferRL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramByteBuffer, paramInt1, paramInt2, paramInt3, paramInt4, paramInt5); + } + + public FloatBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 2) + this.offset; + assert (m >= 0); + return new ByteBufferAsFloatBufferRL(this.bb, -1, 0, k, k, m); + } + + public FloatBuffer duplicate() + { + return new ByteBufferAsFloatBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public FloatBuffer asReadOnlyBuffer() + { + return duplicate(); + } + + public FloatBuffer put(float paramFloat) + { + throw new ReadOnlyBufferException(); + } + + public FloatBuffer put(int paramInt, float paramFloat) + { + throw new ReadOnlyBufferException(); + } + + public FloatBuffer compact() + { + throw new ReadOnlyBufferException(); + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return true; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsIntBufferL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsIntBufferL.java new file mode 100644 index 000000000..2dc9eeff8 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsIntBufferL.java @@ -0,0 +1,119 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsIntBufferL + extends IntBuffer +{ + protected final ByteBuffer bb; + protected final int offset; + + ByteBufferAsIntBufferL(ByteBuffer paramByteBuffer) + { + super(-1, 0, paramByteBuffer + .remaining() >> 2, paramByteBuffer + .remaining() >> 2); + this.bb = paramByteBuffer; + + int i = capacity(); + limit(i); + int j = position(); + assert (j <= i); + this.offset = j; + } + + ByteBufferAsIntBufferL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramInt1, paramInt2, paramInt3, paramInt4); + this.bb = paramByteBuffer; + this.offset = paramInt5; + } + + public IntBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 2) + this.offset; + assert (m >= 0); + return new ByteBufferAsIntBufferL(this.bb, -1, 0, k, k, m); + } + + public IntBuffer duplicate() + { + return new ByteBufferAsIntBufferL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public IntBuffer asReadOnlyBuffer() + { + return new ByteBufferAsIntBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + protected int ix(int paramInt) + { + return (paramInt << 2) + this.offset; + } + + public int get() + { + return Bits.getIntL(this.bb, ix(nextGetIndex())); + } + + public int get(int paramInt) + { + return Bits.getIntL(this.bb, ix(checkIndex(paramInt))); + } + + public IntBuffer put(int paramInt) + { + Bits.putIntL(this.bb, ix(nextPutIndex()), paramInt); + return this; + } + + public IntBuffer put(int paramInt1, int paramInt2) + { + Bits.putIntL(this.bb, ix(checkIndex(paramInt1)), paramInt2); + return this; + } + + public IntBuffer compact() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + + ByteBuffer localByteBuffer1 = this.bb.duplicate(); + localByteBuffer1.limit(ix(j)); + localByteBuffer1.position(ix(0)); + ByteBuffer localByteBuffer2 = localByteBuffer1.slice(); + localByteBuffer2.position(i << 2); + localByteBuffer2.compact(); + position(k); + limit(capacity()); + discardMark(); + return this; + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return false; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsIntBufferRL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsIntBufferRL.java new file mode 100644 index 000000000..042f73502 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsIntBufferRL.java @@ -0,0 +1,70 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsIntBufferRL + extends ByteBufferAsIntBufferL +{ + ByteBufferAsIntBufferRL(ByteBuffer paramByteBuffer) + { + super(paramByteBuffer); + } + + ByteBufferAsIntBufferRL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramByteBuffer, paramInt1, paramInt2, paramInt3, paramInt4, paramInt5); + } + + public IntBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 2) + this.offset; + assert (m >= 0); + return new ByteBufferAsIntBufferRL(this.bb, -1, 0, k, k, m); + } + + public IntBuffer duplicate() + { + return new ByteBufferAsIntBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public IntBuffer asReadOnlyBuffer() + { + return duplicate(); + } + + public IntBuffer put(int paramInt) + { + throw new ReadOnlyBufferException(); + } + + public IntBuffer put(int paramInt1, int paramInt2) + { + throw new ReadOnlyBufferException(); + } + + public IntBuffer compact() + { + throw new ReadOnlyBufferException(); + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return true; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsLongBufferL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsLongBufferL.java new file mode 100644 index 000000000..a06ebdd0f --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsLongBufferL.java @@ -0,0 +1,119 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsLongBufferL + extends LongBuffer +{ + protected final ByteBuffer bb; + protected final int offset; + + ByteBufferAsLongBufferL(ByteBuffer paramByteBuffer) + { + super(-1, 0, paramByteBuffer + .remaining() >> 3, paramByteBuffer + .remaining() >> 3); + this.bb = paramByteBuffer; + + int i = capacity(); + limit(i); + int j = position(); + assert (j <= i); + this.offset = j; + } + + ByteBufferAsLongBufferL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramInt1, paramInt2, paramInt3, paramInt4); + this.bb = paramByteBuffer; + this.offset = paramInt5; + } + + public LongBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 3) + this.offset; + assert (m >= 0); + return new ByteBufferAsLongBufferL(this.bb, -1, 0, k, k, m); + } + + public LongBuffer duplicate() + { + return new ByteBufferAsLongBufferL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public LongBuffer asReadOnlyBuffer() + { + return new ByteBufferAsLongBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + protected int ix(int paramInt) + { + return (paramInt << 3) + this.offset; + } + + public long get() + { + return Bits.getLongL(this.bb, ix(nextGetIndex())); + } + + public long get(int paramInt) + { + return Bits.getLongL(this.bb, ix(checkIndex(paramInt))); + } + + public LongBuffer put(long paramLong) + { + Bits.putLongL(this.bb, ix(nextPutIndex()), paramLong); + return this; + } + + public LongBuffer put(int paramInt, long paramLong) + { + Bits.putLongL(this.bb, ix(checkIndex(paramInt)), paramLong); + return this; + } + + public LongBuffer compact() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + + ByteBuffer localByteBuffer1 = this.bb.duplicate(); + localByteBuffer1.limit(ix(j)); + localByteBuffer1.position(ix(0)); + ByteBuffer localByteBuffer2 = localByteBuffer1.slice(); + localByteBuffer2.position(i << 3); + localByteBuffer2.compact(); + position(k); + limit(capacity()); + discardMark(); + return this; + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return false; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsLongBufferRL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsLongBufferRL.java new file mode 100644 index 000000000..c07ec31c0 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsLongBufferRL.java @@ -0,0 +1,70 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsLongBufferRL + extends ByteBufferAsLongBufferL +{ + ByteBufferAsLongBufferRL(ByteBuffer paramByteBuffer) + { + super(paramByteBuffer); + } + + ByteBufferAsLongBufferRL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramByteBuffer, paramInt1, paramInt2, paramInt3, paramInt4, paramInt5); + } + + public LongBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 3) + this.offset; + assert (m >= 0); + return new ByteBufferAsLongBufferRL(this.bb, -1, 0, k, k, m); + } + + public LongBuffer duplicate() + { + return new ByteBufferAsLongBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public LongBuffer asReadOnlyBuffer() + { + return duplicate(); + } + + public LongBuffer put(long paramLong) + { + throw new ReadOnlyBufferException(); + } + + public LongBuffer put(int paramInt, long paramLong) + { + throw new ReadOnlyBufferException(); + } + + public LongBuffer compact() + { + throw new ReadOnlyBufferException(); + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return true; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsShortBufferL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsShortBufferL.java new file mode 100644 index 000000000..cf4affad1 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsShortBufferL.java @@ -0,0 +1,119 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsShortBufferL + extends ShortBuffer +{ + protected final ByteBuffer bb; + protected final int offset; + + ByteBufferAsShortBufferL(ByteBuffer paramByteBuffer) + { + super(-1, 0, paramByteBuffer + .remaining() >> 1, paramByteBuffer + .remaining() >> 1); + this.bb = paramByteBuffer; + + int i = capacity(); + limit(i); + int j = position(); + assert (j <= i); + this.offset = j; + } + + ByteBufferAsShortBufferL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramInt1, paramInt2, paramInt3, paramInt4); + this.bb = paramByteBuffer; + this.offset = paramInt5; + } + + public ShortBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 1) + this.offset; + assert (m >= 0); + return new ByteBufferAsShortBufferL(this.bb, -1, 0, k, k, m); + } + + public ShortBuffer duplicate() + { + return new ByteBufferAsShortBufferL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public ShortBuffer asReadOnlyBuffer() + { + return new ByteBufferAsShortBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + protected int ix(int paramInt) + { + return (paramInt << 1) + this.offset; + } + + public short get() + { + return Bits.getShortL(this.bb, ix(nextGetIndex())); + } + + public short get(int paramInt) + { + return Bits.getShortL(this.bb, ix(checkIndex(paramInt))); + } + + public ShortBuffer put(short paramShort) + { + Bits.putShortL(this.bb, ix(nextPutIndex()), paramShort); + return this; + } + + public ShortBuffer put(int paramInt, short paramShort) + { + Bits.putShortL(this.bb, ix(checkIndex(paramInt)), paramShort); + return this; + } + + public ShortBuffer compact() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + + ByteBuffer localByteBuffer1 = this.bb.duplicate(); + localByteBuffer1.limit(ix(j)); + localByteBuffer1.position(ix(0)); + ByteBuffer localByteBuffer2 = localByteBuffer1.slice(); + localByteBuffer2.position(i << 1); + localByteBuffer2.compact(); + position(k); + limit(capacity()); + discardMark(); + return this; + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return false; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsShortBufferRL.java b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsShortBufferRL.java new file mode 100644 index 000000000..72ab69eb2 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/java/nio/ByteBufferAsShortBufferRL.java @@ -0,0 +1,70 @@ +package java.nio; // decompiled using http://jd.benow.ca/ + +class ByteBufferAsShortBufferRL + extends ByteBufferAsShortBufferL +{ + ByteBufferAsShortBufferRL(ByteBuffer paramByteBuffer) + { + super(paramByteBuffer); + } + + ByteBufferAsShortBufferRL(ByteBuffer paramByteBuffer, int paramInt1, int paramInt2, int paramInt3, int paramInt4, int paramInt5) + { + super(paramByteBuffer, paramInt1, paramInt2, paramInt3, paramInt4, paramInt5); + } + + public ShortBuffer slice() + { + int i = position(); + int j = limit(); + assert (i <= j); + int k = i <= j ? j - i : 0; + int m = (i << 1) + this.offset; + assert (m >= 0); + return new ByteBufferAsShortBufferRL(this.bb, -1, 0, k, k, m); + } + + public ShortBuffer duplicate() + { + return new ByteBufferAsShortBufferRL(this.bb, + markValue(), + position(), + limit(), + capacity(), this.offset); + } + + public ShortBuffer asReadOnlyBuffer() + { + return duplicate(); + } + + public ShortBuffer put(short paramShort) + { + throw new ReadOnlyBufferException(); + } + + public ShortBuffer put(int paramInt, short paramShort) + { + throw new ReadOnlyBufferException(); + } + + public ShortBuffer compact() + { + throw new ReadOnlyBufferException(); + } + + public boolean isDirect() + { + return this.bb.isDirect(); + } + + public boolean isReadOnly() + { + return true; + } + + public ByteOrder order() + { + return ByteOrder.BIG_ENDIAN; + } +} diff --git a/sources/net.sf.j2s.java.core/src/java/nio/HeapByteBuffer.java b/sources/net.sf.j2s.java.core/src/java/nio/HeapByteBuffer.java index 0bf7af2df..0e451d610 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/HeapByteBuffer.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/HeapByteBuffer.java @@ -58,14 +58,17 @@ protected HeapByteBuffer(byte[] buf, int mark, int pos, int lim, int cap, int of */ } + @Override public ByteBuffer slice() { return new HeapByteBuffer(hb, -1, 0, this.remaining(), this.remaining(), this.position() + offset); } + @Override public ByteBuffer duplicate() { return new HeapByteBuffer(hb, this.markValue(), this.position(), this.limit(), this.capacity(), offset); } + @Override public ByteBuffer asReadOnlyBuffer() { return new HeapByteBufferR(hb, this.markValue(), this.position(), this.limit(), this.capacity(), offset); } @@ -74,14 +77,17 @@ protected int ix(int i) { return i + offset; } + @Override public byte get() { return hb[ix(nextGetIndex())]; } + @Override public byte get(int i) { return hb[ix(checkIndex(i))]; } + @Override public ByteBuffer get(byte[] dst, int offset, int length) { checkBounds(offset, length, dst.length); if (length > remaining()) @@ -91,25 +97,30 @@ public ByteBuffer get(byte[] dst, int offset, int length) { return this; } + @Override public boolean isDirect() { return false; } + @Override public boolean isReadOnly() { return false; } + @Override public ByteBuffer put(byte x) { hb[ix(nextPutIndex())] = x; return this; } + @Override public ByteBuffer put(int i, byte x) { hb[ix(checkIndex(i))] = x; return this; } + @Override public ByteBuffer put(byte[] src, int offset, int length) { checkBounds(offset, length, src.length); @@ -120,6 +131,7 @@ public ByteBuffer put(byte[] src, int offset, int length) { return this; } + @Override public ByteBuffer put(ByteBuffer src) { if (src instanceof HeapByteBuffer) { @@ -144,6 +156,7 @@ public ByteBuffer put(ByteBuffer src) { return this; } + @Override public ByteBuffer compact() { System.arraycopy(hb, ix(position()), hb, ix(0), remaining()); @@ -153,10 +166,12 @@ public ByteBuffer compact() { return this; } + @Override public byte _get(int i) { // package-private return hb[i]; } + @Override public void _put(int i, byte b) { // package-private hb[i] = b; @@ -184,6 +199,7 @@ public ByteBuffer putChar(int i, char x) { return this; } + @Override public CharBuffer asCharBuffer() { int size = this.remaining() >> 1; int off = offset + position(); @@ -213,6 +229,7 @@ public ByteBuffer putShort(int i, short x) { return this; } + @Override public ShortBuffer asShortBuffer() { int size = this.remaining() >> 1; int off = offset + position(); @@ -242,6 +259,7 @@ public ByteBuffer putInt(int i, int x) { return this; } + @Override public IntBuffer asIntBuffer() { int size = this.remaining() >> 2; int off = offset + position(); @@ -271,6 +289,7 @@ public ByteBuffer putLong(int i, long x) { return this; } + @Override public LongBuffer asLongBuffer() { int size = this.remaining() >> 3; int off = offset + position(); @@ -300,6 +319,7 @@ public ByteBuffer putFloat(int i, float x) { return this; } + @Override public FloatBuffer asFloatBuffer() { int size = this.remaining() >> 2; int off = offset + position(); @@ -329,6 +349,7 @@ public ByteBuffer putDouble(int i, double x) { return this; } + @Override public DoubleBuffer asDoubleBuffer() { int size = this.remaining() >> 3; int off = offset + position(); diff --git a/sources/net.sf.j2s.java.core/src/java/nio/HeapByteBufferR.java b/sources/net.sf.j2s.java.core/src/java/nio/HeapByteBufferR.java index 7ded20ac6..58e99eaa7 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/HeapByteBufferR.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/HeapByteBufferR.java @@ -21,7 +21,8 @@ protected HeapByteBufferR(byte[] paramArrayOfByte, int paramInt1, int paramInt2, this.isReadOnly = true; } - public ByteBuffer slice() + @Override +public ByteBuffer slice() { return new HeapByteBufferR(this.hb, -1, 0, @@ -30,7 +31,8 @@ public ByteBuffer slice() position() + this.offset); } - public ByteBuffer duplicate() + @Override +public ByteBuffer duplicate() { return new HeapByteBufferR(this.hb, markValue(), @@ -39,147 +41,174 @@ public ByteBuffer duplicate() capacity(), this.offset); } - public ByteBuffer asReadOnlyBuffer() + @Override +public ByteBuffer asReadOnlyBuffer() { return duplicate(); } - public boolean isReadOnly() + @Override +public boolean isReadOnly() { return true; } - public ByteBuffer put(byte paramByte) + @Override +public ByteBuffer put(byte paramByte) { throw new ReadOnlyBufferException(); } - public ByteBuffer put(int paramInt, byte paramByte) + @Override +public ByteBuffer put(int paramInt, byte paramByte) { throw new ReadOnlyBufferException(); } - public ByteBuffer put(byte[] paramArrayOfByte, int paramInt1, int paramInt2) + @Override +public ByteBuffer put(byte[] paramArrayOfByte, int paramInt1, int paramInt2) { throw new ReadOnlyBufferException(); } - public ByteBuffer put(ByteBuffer paramByteBuffer) + @Override +public ByteBuffer put(ByteBuffer paramByteBuffer) { throw new ReadOnlyBufferException(); } - public ByteBuffer compact() + @Override +public ByteBuffer compact() { throw new ReadOnlyBufferException(); } - public byte _get(int paramInt) + @Override +public byte _get(int paramInt) { return this.hb[paramInt]; } - public void _put(int paramInt, byte paramByte) + @Override +public void _put(int paramInt, byte paramByte) { throw new ReadOnlyBufferException(); } - public ByteBuffer putChar(char paramChar) + @Override +public ByteBuffer putChar(char paramChar) { throw new ReadOnlyBufferException(); } - public ByteBuffer putChar(int paramInt, char paramChar) + @Override +public ByteBuffer putChar(int paramInt, char paramChar) { throw new ReadOnlyBufferException(); } - public CharBuffer asCharBuffer() + @Override +public CharBuffer asCharBuffer() { int i = remaining() >> 1; int j = this.offset + position(); return this.bigEndian ? new ByteBufferAsCharBufferRB(this, -1, 0, i, i, j) : new ByteBufferAsCharBufferRL(this, -1, 0, i, i, j); } - public ByteBuffer putShort(short paramShort) + @Override +public ByteBuffer putShort(short paramShort) { throw new ReadOnlyBufferException(); } - public ByteBuffer putShort(int paramInt, short paramShort) + @Override +public ByteBuffer putShort(int paramInt, short paramShort) { throw new ReadOnlyBufferException(); } - public ShortBuffer asShortBuffer() + @Override +public ShortBuffer asShortBuffer() { int i = remaining() >> 1; int j = this.offset + position(); return this.bigEndian ? new ByteBufferAsShortBufferRB(this, -1, 0, i, i, j) : new ByteBufferAsShortBufferRL(this, -1, 0, i, i, j); } - public ByteBuffer putInt(int paramInt) + @Override +public ByteBuffer putInt(int paramInt) { throw new ReadOnlyBufferException(); } - public ByteBuffer putInt(int paramInt1, int paramInt2) + @Override +public ByteBuffer putInt(int paramInt1, int paramInt2) { throw new ReadOnlyBufferException(); } - public IntBuffer asIntBuffer() + @Override +public IntBuffer asIntBuffer() { int i = remaining() >> 2; int j = this.offset + position(); return this.bigEndian ? new ByteBufferAsIntBufferRB(this, -1, 0, i, i, j) : new ByteBufferAsIntBufferRL(this, -1, 0, i, i, j); } - public ByteBuffer putLong(long paramLong) + @Override +public ByteBuffer putLong(long paramLong) { throw new ReadOnlyBufferException(); } - public ByteBuffer putLong(int paramInt, long paramLong) + @Override +public ByteBuffer putLong(int paramInt, long paramLong) { throw new ReadOnlyBufferException(); } - public LongBuffer asLongBuffer() + @Override +public LongBuffer asLongBuffer() { int i = remaining() >> 3; int j = this.offset + position(); return this.bigEndian ? new ByteBufferAsLongBufferRB(this, -1, 0, i, i, j) : new ByteBufferAsLongBufferRL(this, -1, 0, i, i, j); } - public ByteBuffer putFloat(float paramFloat) + @Override +public ByteBuffer putFloat(float paramFloat) { throw new ReadOnlyBufferException(); } - public ByteBuffer putFloat(int paramInt, float paramFloat) + @Override +public ByteBuffer putFloat(int paramInt, float paramFloat) { throw new ReadOnlyBufferException(); } - public FloatBuffer asFloatBuffer() + @Override +public FloatBuffer asFloatBuffer() { int i = remaining() >> 2; int j = this.offset + position(); return this.bigEndian ? new ByteBufferAsFloatBufferRB(this, -1, 0, i, i, j) : new ByteBufferAsFloatBufferRL(this, -1, 0, i, i, j); } - public ByteBuffer putDouble(double paramDouble) + @Override +public ByteBuffer putDouble(double paramDouble) { throw new ReadOnlyBufferException(); } - public ByteBuffer putDouble(int paramInt, double paramDouble) + @Override +public ByteBuffer putDouble(int paramInt, double paramDouble) { throw new ReadOnlyBufferException(); } - public DoubleBuffer asDoubleBuffer() + @Override +public DoubleBuffer asDoubleBuffer() { int i = remaining() >> 3; int j = this.offset + position(); diff --git a/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractInterruptibleChannel.java b/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractInterruptibleChannel.java index 5000c583c..ab7a30a3b 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractInterruptibleChannel.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractInterruptibleChannel.java @@ -89,7 +89,7 @@ public abstract class AbstractInterruptibleChannel { private final Object closeLock = new Object(); - private volatile boolean open = true; + protected volatile boolean open = true; /** * Initializes a new instance of this class. @@ -141,9 +141,9 @@ public final boolean isOpen() { // -- Interruption machinery -- - private Interruptible interruptor; - private volatile Thread interrupted; - +// private Interruptible interruptor; +// private volatile Thread interrupted; +// /** * Marks the beginning of an I/O operation that might block indefinitely. * @@ -153,24 +153,24 @@ public final boolean isOpen() { * closing and interruption for this channel.

*/ protected final void begin() { - if (interruptor == null) { - interruptor = new Interruptible() { - public void interrupt(Thread target) { - synchronized (closeLock) { - if (!open) - return; - open = false; - interrupted = target; - try { - AbstractInterruptibleChannel.this.implCloseChannel(); - } catch (IOException x) { } - } - }}; - } - blockedOn(interruptor); - Thread me = Thread.currentThread(); - if (me.isInterrupted()) - interruptor.interrupt(me); +// if (interruptor == null) { +// interruptor = new Interruptible() { +// public void interrupt(Thread target) { +// synchronized (closeLock) { +// if (!open) +// return; +// open = false; +// interrupted = target; +// try { +// AbstractInterruptibleChannel.this.implCloseChannel(); +// } catch (IOException x) { } +// } +// }}; +// } +// blockedOn(interruptor); +// Thread me = Thread.currentThread(); +// if (me.isInterrupted()) +// interruptor.interrupt(me); } /** @@ -195,20 +195,20 @@ public void interrupt(Thread target) { protected final void end(boolean completed) throws AsynchronousCloseException { - blockedOn(null); - Thread interrupted = this.interrupted; - if (interrupted != null && interrupted == Thread.currentThread()) { - interrupted = null; - throw new ClosedByInterruptException(); - } +// blockedOn(null); +// Thread interrupted = this.interrupted; +// if (interrupted != null && interrupted == Thread.currentThread()) { +// interrupted = null; +// throw new ClosedByInterruptException(); +// } if (!completed && !open) throw new AsynchronousCloseException(); } - - // -- sun.misc.SharedSecrets -- - static void blockedOn(Interruptible intr) { // package-private - sun.misc.SharedSecrets.getJavaLangAccess().blockedOn(Thread.currentThread(), - intr); - } +// +// // -- sun.misc.SharedSecrets -- +// static void blockedOn(Interruptible intr) { // package-private +// sun.misc.SharedSecrets.getJavaLangAccess().blockedOn(Thread.currentThread(), +// intr); +// } } diff --git a/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractSelectableChannel.java b/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractSelectableChannel.java index 0de212d2d..5e481aaf0 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractSelectableChannel.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractSelectableChannel.java @@ -85,7 +85,8 @@ protected AbstractSelectableChannel(SelectorProvider provider) { * * @return The provider that created this channel */ - public final SelectorProvider provider() { + @Override + public final SelectorProvider provider() { return provider; } @@ -152,13 +153,15 @@ private boolean haveValidKeys() { // -- Registration -- - public final boolean isRegistered() { + @Override + public final boolean isRegistered() { synchronized (keyLock) { return keyCount != 0; } } - public final SelectionKey keyFor(Selector sel) { + @Override + public final SelectionKey keyFor(Selector sel) { return findKey(sel); } @@ -188,7 +191,8 @@ public final SelectionKey keyFor(Selector sel) { * * @throws IllegalArgumentException {@inheritDoc} */ - public final SelectionKey register(Selector sel, int ops, + @Override + public final SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException { @@ -230,7 +234,8 @@ public final SelectionKey register(Selector sel, int ops, * order to perform the actual work of closing this channel. It then * cancels all of this channel's keys.

*/ - protected final void implCloseChannel() throws IOException { + @Override + protected final void implCloseChannel() throws IOException { implCloseSelectableChannel(); synchronized (keyLock) { int count = (keys == null) ? 0 : keys.length; @@ -263,13 +268,15 @@ protected final void implCloseChannel() throws IOException { // -- Blocking -- - public final boolean isBlocking() { + @Override + public final boolean isBlocking() { synchronized (regLock) { return blocking; } } - public final Object blockingLock() { + @Override + public final Object blockingLock() { return regLock; } @@ -281,7 +288,8 @@ public final Object blockingLock() { * implConfigureBlocking} method, while holding the appropriate locks, in * order to change the mode.

*/ - public final SelectableChannel configureBlocking(boolean block) + @Override + public final SelectableChannel configureBlocking(boolean block) throws IOException { synchronized (regLock) { diff --git a/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractSelector.java b/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractSelector.java index 09e3b6149..d5c5cd30b 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractSelector.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/channels/spi/AbstractSelector.java @@ -207,16 +207,16 @@ protected final void deregister(AbstractSelectionKey key) { * blocked in an I/O operation upon the selector.

*/ protected final void begin() { - if (interruptor == null) { - interruptor = new Interruptible() { - public void interrupt(Thread ignore) { - AbstractSelector.this.wakeup(); - }}; - } - AbstractInterruptibleChannel.blockedOn(interruptor); - Thread me = Thread.currentThread(); - if (me.isInterrupted()) - interruptor.interrupt(me); +// if (interruptor == null) { +// interruptor = new Interruptible() { +// public void interrupt(Thread ignore) { +// AbstractSelector.this.wakeup(); +// }}; +// } +// AbstractInterruptibleChannel.blockedOn(interruptor); +// Thread me = Thread.currentThread(); +// if (me.isInterrupted()) +// interruptor.interrupt(me); } /** @@ -228,7 +228,7 @@ public void interrupt(Thread ignore) { * this selector.

*/ protected final void end() { - AbstractInterruptibleChannel.blockedOn(null); +// AbstractInterruptibleChannel.blockedOn(null); } } diff --git a/sources/net.sf.j2s.java.core/src/java/nio/file/FileSystem.java b/sources/net.sf.j2s.java.core/src/java/nio/file/FileSystem.java index 03ace89e7..d0962c227 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/file/FileSystem.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/file/FileSystem.java @@ -32,6 +32,9 @@ import java.io.IOException; /** + * + * Extended by swingjs.JSFileSytem + * * Provides an interface to a file system and is the factory for objects to * access files and other objects in the file system. * diff --git a/sources/net.sf.j2s.java.core/src/java/nio/file/Path.java b/sources/net.sf.j2s.java.core/src/java/nio/file/Path.java index daa09f4cf..41c93858e 100644 --- a/sources/net.sf.j2s.java.core/src/java/nio/file/Path.java +++ b/sources/net.sf.j2s.java.core/src/java/nio/file/Path.java @@ -772,7 +772,8 @@ WatchKey register(WatchService watcher, * @return {@code true} if, and only if, the given object is a {@code Path} * that is identical to this {@code Path} */ - boolean equals(Object other); + @Override + boolean equals(Object other); /** * Computes a hash code for this path. @@ -783,7 +784,8 @@ WatchKey register(WatchService watcher, * * @return the hash-code value for this path */ - int hashCode(); + @Override + int hashCode(); /** * Returns the string representation of this path. @@ -797,5 +799,6 @@ WatchKey register(WatchService watcher, * * @return the string representation of this path */ - String toString(); + @Override + String toString(); } diff --git a/sources/net.sf.j2s.java.core/src/java/util/Arrays.java b/sources/net.sf.j2s.java.core/src/java/util/Arrays.java index 49f4db2ee..e7184335e 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/Arrays.java +++ b/sources/net.sf.j2s.java.core/src/java/util/Arrays.java @@ -97,8 +97,8 @@ private Arrays() {} * based implementation. */ static final class NaturalOrder implements Comparator { - @SuppressWarnings("unchecked") - public int compare(Object first, Object second) { + @Override + public int compare(Object first, Object second) { return ((Comparable)first).compareTo(second); } static final NaturalOrder INSTANCE = new NaturalOrder(); @@ -3177,8 +3177,13 @@ public static void fill(Object[] a, int fromIndex, int toIndex, Object val) { * @throws NullPointerException if original is null * @since 1.6 */ - @SuppressWarnings("unchecked") public static T[] copyOf(T[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } return (T[]) copyOf(original, newLength, original.getClass()); } @@ -3207,7 +3212,6 @@ public static T[] copyOf(T[] original, int newLength) { * @since 1.6 */ public static T[] copyOf(U[] original, int newLength, Class newType) { - @SuppressWarnings("unchecked") T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); @@ -3234,6 +3238,12 @@ public static T[] copyOf(U[] original, int newLength, Class * @since 1.6 */ public static byte[] copyOf(byte[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } byte[] copy = new byte[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); @@ -3258,6 +3268,12 @@ public static byte[] copyOf(byte[] original, int newLength) { * @since 1.6 */ public static short[] copyOf(short[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } short[] copy = new short[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); @@ -3282,6 +3298,12 @@ public static short[] copyOf(short[] original, int newLength) { * @since 1.6 */ public static int[] copyOf(int[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } int[] copy = new int[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); @@ -3306,6 +3328,12 @@ public static int[] copyOf(int[] original, int newLength) { * @since 1.6 */ public static long[] copyOf(long[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } long[] copy = new long[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); @@ -3330,6 +3358,12 @@ public static long[] copyOf(long[] original, int newLength) { * @since 1.6 */ public static char[] copyOf(char[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } char[] copy = new char[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); @@ -3354,6 +3388,12 @@ public static char[] copyOf(char[] original, int newLength) { * @since 1.6 */ public static float[] copyOf(float[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } float[] copy = new float[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); @@ -3378,6 +3418,12 @@ public static float[] copyOf(float[] original, int newLength) { * @since 1.6 */ public static double[] copyOf(double[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } double[] copy = new double[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); @@ -3402,6 +3448,12 @@ public static double[] copyOf(double[] original, int newLength) { * @since 1.6 */ public static boolean[] copyOf(boolean[] original, int newLength) { + if (newLength <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, 0, newLength); + */ + } boolean[] copy = new boolean[newLength]; System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); @@ -3439,6 +3491,12 @@ public static boolean[] copyOf(boolean[] original, int newLength) { */ @SuppressWarnings("unchecked") public static T[] copyOfRange(T[] original, int from, int to) { + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } return copyOfRange(original, from, to, (Class) original.getClass()); } @@ -3518,6 +3576,12 @@ public static byte[] copyOfRange(byte[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } byte[] copy = new byte[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); @@ -3554,6 +3618,12 @@ public static short[] copyOfRange(short[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } short[] copy = new short[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); @@ -3590,6 +3660,12 @@ public static int[] copyOfRange(int[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } int[] copy = new int[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); @@ -3626,6 +3702,12 @@ public static long[] copyOfRange(long[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } long[] copy = new long[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); @@ -3662,6 +3744,12 @@ public static char[] copyOfRange(char[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } char[] copy = new char[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); @@ -3698,6 +3786,12 @@ public static float[] copyOfRange(float[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } float[] copy = new float[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); @@ -3734,6 +3828,12 @@ public static double[] copyOfRange(double[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } double[] copy = new double[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); @@ -3770,6 +3870,12 @@ public static boolean[] copyOfRange(boolean[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + if (to <= original.length) { + /** + * @j2sNative + * return Clazz.array(-1, original, from, to); + */ + } boolean[] copy = new boolean[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); @@ -3796,7 +3902,6 @@ public static boolean[] copyOfRange(boolean[] original, int from, int to) { * @return a list view of the specified array */ @SafeVarargs - @SuppressWarnings("varargs") public static List asList(T... a) { return new ArrayList<>(a); } @@ -3825,7 +3930,6 @@ public Object[] toArray() { } @Override - @SuppressWarnings("unchecked") public T[] toArray(T[] a) { int size = size(); if (a.length < size) diff --git a/sources/net.sf.j2s.java.core/src/java/util/EnumSet.java b/sources/net.sf.j2s.java.core/src/java/util/EnumSet.java index bf76a970c..793ba0630 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/EnumSet.java +++ b/sources/net.sf.j2s.java.core/src/java/util/EnumSet.java @@ -25,8 +25,6 @@ package java.util; -import sun.misc.SharedSecrets; - /** * A specialized {@link Set} implementation for use with enum types. All of * the elements in an enum set must come from a single enum type that is @@ -77,6 +75,7 @@ * @see EnumMap * @serial exclude */ +@SuppressWarnings("serial") public abstract class EnumSet> extends AbstractSet implements Cloneable, java.io.Serializable { @@ -90,7 +89,7 @@ public abstract class EnumSet> extends AbstractSet */ final Enum[] universe; - private static Enum[] ZERO_LENGTH_ENUM_ARRAY = new Enum[0]; +// private static Enum[] ZERO_LENGTH_ENUM_ARRAY = new Enum[0]; EnumSet(ClasselementType, Enum[] universe) { this.elementType = elementType; @@ -111,7 +110,7 @@ public static > EnumSet noneOf(Class elementType) { if (universe == null) throw new ClassCastException(elementType + " not an enum"); - if (universe.length <= 64) + if (universe.length <= 32) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); @@ -375,7 +374,7 @@ public static > EnumSet range(E from, E to) { * * @return a copy of this set */ - @SuppressWarnings("unchecked") + @Override public EnumSet clone() { try { return (EnumSet) super.clone(); @@ -409,58 +408,56 @@ private static > E[] getUniverse(Class elementType) { // .getEnumConstantsShared(elementType); } - /** - * This class is used to serialize all EnumSet instances, regardless of - * implementation type. It captures their "logical contents" and they - * are reconstructed using public static factories. This is necessary - * to ensure that the existence of a particular implementation type is - * an implementation detail. - * - * @serial include - */ - private static class SerializationProxy > - implements java.io.Serializable - { - /** - * The element type of this enum set. - * - * @serial - */ - private final Class elementType; - - /** - * The elements contained in this enum set. - * - * @serial - */ - private final Enum[] elements; - - SerializationProxy(EnumSet set) { - elementType = set.elementType; - elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY); - } - - // instead of cast to E, we should perhaps use elementType.cast() - // to avoid injection of forged stream, but it will slow the implementation - @SuppressWarnings("unchecked") - private Object readResolve() { - EnumSet result = EnumSet.noneOf(elementType); - for (Enum e : elements) - result.add((E)e); - return result; - } - - private static final long serialVersionUID = 362491234563181265L; - } - - Object writeReplace() { - return new SerializationProxy<>(this); - } - - // readObject method for the serialization proxy pattern - // See Effective Java, Second Ed., Item 78. - private void readObject(java.io.ObjectInputStream stream) - throws java.io.InvalidObjectException { - throw new java.io.InvalidObjectException("Proxy required"); - } +// /** +// * This class is used to serialize all EnumSet instances, regardless of +// * implementation type. It captures their "logical contents" and they +// * are reconstructed using public static factories. This is necessary +// * to ensure that the existence of a particular implementation type is +// * an implementation detail. +// * +// * @serial include +// */ +// private static class SerializationProxy > +// implements java.io.Serializable +// { +// /** +// * The element type of this enum set. +// * +// * @serial +// */ +// private final Class elementType; +// +// /** +// * The elements contained in this enum set. +// * +// * @serial +// */ +// private final Enum[] elements; +// +// SerializationProxy(EnumSet set) { +// elementType = set.elementType; +// elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY); +// } +// +// // instead of cast to E, we should perhaps use elementType.cast() +// // to avoid injection of forged stream, but it will slow the implementation +// @SuppressWarnings("unchecked") +// private Object readResolve() { +// EnumSet result = EnumSet.noneOf(elementType); +// for (Enum e : elements) +// result.add((E)e); +// return result; +// } +// } +// +// Object writeReplace() { +// return new SerializationProxy<>(this); +// } +// +// // readObject method for the serialization proxy pattern +// // See Effective Java, Second Ed., Item 78. +// private void readObject(java.io.ObjectInputStream stream) +// throws java.io.InvalidObjectException { +// throw new java.io.InvalidObjectException("Proxy required"); +// } } diff --git a/sources/net.sf.j2s.java.core/src/java/util/Hashtable.java b/sources/net.sf.j2s.java.core/src/java/util/Hashtable.java index f79cb860b..d05e0b4b8 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/Hashtable.java +++ b/sources/net.sf.j2s.java.core/src/java/util/Hashtable.java @@ -25,11 +25,11 @@ package java.util; -import java.io.*; -import java.util.concurrent.ThreadLocalRandom; +import java.io.IOException; +import java.io.StreamCorruptedException; import java.util.function.BiConsumer; -import java.util.function.Function; import java.util.function.BiFunction; +import java.util.function.Function; /** * This class implements a hash table, which maps keys to values. Any @@ -230,7 +230,8 @@ public Hashtable(Map t) { * * @return the number of keys in this hashtable. */ - public synchronized int size() { + @Override + public synchronized int size() { return count; } @@ -240,7 +241,8 @@ public synchronized int size() { * @return true if this hashtable maps no keys to values; * false otherwise. */ - public synchronized boolean isEmpty() { + @Override + public synchronized boolean isEmpty() { return count == 0; } @@ -253,7 +255,8 @@ public synchronized boolean isEmpty() { * @see #keySet() * @see Map */ - public synchronized Enumeration keys() { + @Override + public synchronized Enumeration keys() { return this.getEnumeration(KEYS); } @@ -268,7 +271,8 @@ public synchronized Enumeration keys() { * @see #values() * @see Map */ - public synchronized Enumeration elements() { + @Override + public synchronized Enumeration elements() { return this.getEnumeration(VALUES); } @@ -316,7 +320,8 @@ public synchronized boolean contains(Object value) { * @throws NullPointerException if the value is null * @since 1.2 */ - public boolean containsValue(Object value) { + @Override + public boolean containsValue(Object value) { return contains(value); } @@ -330,7 +335,8 @@ public boolean containsValue(Object value) { * @throws NullPointerException if the key is null * @see #contains(Object) */ - public synchronized boolean containsKey(Object key) { + @Override + public synchronized boolean containsKey(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @@ -357,7 +363,8 @@ public synchronized boolean containsKey(Object key) { * @throws NullPointerException if the specified key is null * @see #put(Object, Object) */ - @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public synchronized V get(Object key) { Entry tab[] = table; int hash = key.hashCode(); @@ -453,7 +460,8 @@ private void addEntry(int hash, K key, V value, int index) { * @see Object#equals(Object) * @see #get(Object) */ - public synchronized V put(K key, V value) { + @Override + public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); @@ -486,7 +494,8 @@ public synchronized V put(K key, V value) { * or null if the key did not have a mapping * @throws NullPointerException if the key is null */ - public synchronized V remove(Object key) { + @Override + public synchronized V remove(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @@ -518,7 +527,8 @@ public synchronized V remove(Object key) { * @throws NullPointerException if the specified map is null * @since 1.2 */ - public synchronized void putAll(Map t) { + @Override + public synchronized void putAll(Map t) { for (Map.Entry e : t.entrySet()) put(e.getKey(), e.getValue()); } @@ -526,7 +536,8 @@ public synchronized void putAll(Map t) { /** * Clears this hashtable so that it contains no keys. */ - public synchronized void clear() { + @Override + public synchronized void clear() { Entry tab[] = table; modCount++; for (int index = tab.length; --index >= 0; ) @@ -541,7 +552,8 @@ public synchronized void clear() { * * @return a clone of the hashtable */ - public synchronized Object clone() { + @Override + public synchronized Object clone() { try { Hashtable t = (Hashtable)super.clone(); t.table = new Entry[table.length]; @@ -570,7 +582,8 @@ public synchronized Object clone() { * * @return a string representation of this hashtable */ - public synchronized String toString() { + @Override + public synchronized String toString() { int max = size() - 1; if (max == -1) return "{}"; @@ -636,26 +649,32 @@ private Iterator getIterator(int type) { * * @since 1.2 */ - public Set keySet() { + @Override + public Set keySet() { if (keySet == null) keySet = Collections.synchronizedSet(new KeySet(), this); return keySet; } private class KeySet extends AbstractSet { - public Iterator iterator() { + @Override + public Iterator iterator() { return getIterator(KEYS); } - public int size() { + @Override + public int size() { return count; } - public boolean contains(Object o) { + @Override + public boolean contains(Object o) { return containsKey(o); } - public boolean remove(Object o) { + @Override + public boolean remove(Object o) { return Hashtable.this.remove(o) != null; } - public void clear() { + @Override + public void clear() { Hashtable.this.clear(); } } @@ -676,22 +695,26 @@ public void clear() { * * @since 1.2 */ - public Set> entrySet() { + @Override + public Set> entrySet() { if (entrySet==null) entrySet = Collections.synchronizedSet(new EntrySet(), this); return entrySet; } private class EntrySet extends AbstractSet> { - public Iterator> iterator() { + @Override + public Iterator> iterator() { return getIterator(ENTRIES); } - public boolean add(Map.Entry o) { + @Override + public boolean add(Map.Entry o) { return super.add(o); } - public boolean contains(Object o) { + @Override + public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry entry = (Map.Entry)o; @@ -706,7 +729,8 @@ public boolean contains(Object o) { return false; } - public boolean remove(Object o) { + @Override + public boolean remove(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry entry = (Map.Entry) o; @@ -733,11 +757,13 @@ public boolean remove(Object o) { return false; } - public int size() { + @Override + public int size() { return count; } - public void clear() { + @Override + public void clear() { Hashtable.this.clear(); } } @@ -757,7 +783,8 @@ public void clear() { * * @since 1.2 */ - public Collection values() { + @Override + public Collection values() { if (values==null) values = Collections.synchronizedCollection(new ValueCollection(), this); @@ -765,16 +792,20 @@ public Collection values() { } private class ValueCollection extends AbstractCollection { - public Iterator iterator() { + @Override + public Iterator iterator() { return getIterator(VALUES); } - public int size() { + @Override + public int size() { return count; } - public boolean contains(Object o) { + @Override + public boolean contains(Object o) { return containsValue(o); } - public void clear() { + @Override + public void clear() { Hashtable.this.clear(); } } @@ -790,7 +821,8 @@ public void clear() { * @see Map#equals(Object) * @since 1.2 */ - public synchronized boolean equals(Object o) { + @Override + public synchronized boolean equals(Object o) { if (o == this) return true; @@ -830,7 +862,8 @@ public synchronized boolean equals(Object o) { * @see Map#hashCode() * @since 1.2 */ - public synchronized int hashCode() { + @Override + public synchronized int hashCode() { /* * This code detects the recursion caused by computing the hash code * of a self-referential hash table and prevents the stack overflow @@ -1243,25 +1276,28 @@ protected Entry(int hash, K key, V value, Entry next) { this.key = key; this.value = value; this.next_ = next; - } + } - @SuppressWarnings("unchecked") - protected Object clone() { + @Override + protected Object clone() { return new Entry<>(hash, key, value, (next_==null ? null : (Entry) next_.clone())); } // Map.Entry Ops - public K getKey() { + @Override + public K getKey() { return key; } - public V getValue() { + @Override + public V getValue() { return value; } - public V setValue(V value) { + @Override + public V setValue(V value) { if (value == null) throw new NullPointerException(); @@ -1270,7 +1306,8 @@ public V setValue(V value) { return oldValue; } - public boolean equals(Object o) { + @Override + public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; @@ -1279,11 +1316,13 @@ public boolean equals(Object o) { (value==null ? e.getValue()==null : value.equals(e.getValue())); } - public int hashCode() { + @Override + public int hashCode() { return hash ^ Objects.hashCode(value); } - public String toString() { + @Override + public String toString() { return key.toString()+"="+value.toString(); } } @@ -1325,7 +1364,8 @@ private class Enumerator implements Enumeration, Iterator { this.iterator = iterator; } - public boolean hasMoreElements() { + @Override + public boolean hasMoreElements() { Entry e = entry; int i = index; Entry[] t = table; @@ -1338,7 +1378,8 @@ public boolean hasMoreElements() { return e != null; } - @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public T nextElement() { Entry et = entry; int i = index; @@ -1358,17 +1399,20 @@ public T nextElement() { } // Iterator methods - public boolean hasNext() { + @Override + public boolean hasNext() { return hasMoreElements(); } - public T next() { + @Override + public T next() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); return nextElement(); } - public void remove() { + @Override + public void remove() { if (!iterator) throw new UnsupportedOperationException(); if (lastReturned == null) diff --git a/sources/net.sf.j2s.java.core/src/java/util/JumboEnumSet.java b/sources/net.sf.j2s.java.core/src/java/util/JumboEnumSet.java index 679851182..85668f4ea 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/JumboEnumSet.java +++ b/sources/net.sf.j2s.java.core/src/java/util/JumboEnumSet.java @@ -26,58 +26,63 @@ package java.util; /** + * SwingJS switched to 32-bit from 64-bit. + * * Private implementation class for EnumSet, for "jumbo" enum types - * (i.e., those with more than 64 elements). + * (i.e., those with more than 32 elements). * * @author Josh Bloch * @since 1.5 * @serial exclude */ +@SuppressWarnings("serial") class JumboEnumSet> extends EnumSet { - private static final long serialVersionUID = 334349849919042784L; /** * Bit vector representation of this set. The ith bit of the jth - * element of this array represents the presence of universe[64*j +i] + * element of this array represents the presence of universe[32*j +i] * in this set. */ - private long elements[]; + private int elements[]; // Redundant - maintained for performance private int size = 0; JumboEnumSet(ClasselementType, Enum[] universe) { super(elementType, universe); - elements = new long[(universe.length + 63) >>> 6]; + elements = new int[(universe.length + 31) >>> 5]; } - void addRange(E from, E to) { - int fromIndex = from.ordinal() >>> 6; - int toIndex = to.ordinal() >>> 6; + @Override + void addRange(E from, E to) { + int fromIndex = from.ordinal() >>> 5; + int toIndex = to.ordinal() >>> 5; if (fromIndex == toIndex) { - elements[fromIndex] = (-1L >>> (from.ordinal() - to.ordinal() - 1)) + elements[fromIndex] = (-1 >>> (from.ordinal() - to.ordinal() - 1)) << from.ordinal(); } else { - elements[fromIndex] = (-1L << from.ordinal()); + elements[fromIndex] = (-1 << from.ordinal()); for (int i = fromIndex + 1; i < toIndex; i++) elements[i] = -1; - elements[toIndex] = -1L >>> (63 - to.ordinal()); + elements[toIndex] = -1 >>> (31 - to.ordinal()); } size = to.ordinal() - from.ordinal() + 1; } - void addAll() { + @Override + void addAll() { for (int i = 0; i < elements.length; i++) elements[i] = -1; elements[elements.length - 1] >>>= -universe.length; size = universe.length; } - void complement() { + @Override + void complement() { for (int i = 0; i < elements.length; i++) elements[i] = ~elements[i]; - elements[elements.length - 1] &= (-1L >>> -universe.length); + elements[elements.length - 1] &= (-1 >>> -universe.length); size = universe.length - size; } @@ -90,16 +95,18 @@ void complement() { * * @return an iterator over the elements contained in this set */ - public Iterator iterator() { + @Override + public Iterator iterator() { return new EnumSetIterator<>(); } - private class EnumSetIterator> implements Iterator { + @SuppressWarnings("hiding") + private class EnumSetIterator> implements Iterator { /** * A bit vector representing the elements in the current "word" * of the set not yet returned by this iterator. */ - long unseen; + int unseen; /** * The index corresponding to unseen in the elements array. @@ -110,7 +117,7 @@ private class EnumSetIterator> implements Iterator { * The bit representing the last element returned by this iterator * but not removed, or zero if no such element exists. */ - long lastReturned = 0; + int lastReturned = 0; /** * The index corresponding to lastReturned in the elements array. @@ -136,15 +143,15 @@ public E next() { lastReturned = unseen & -unseen; lastReturnedIndex = unseenIndex; unseen -= lastReturned; - return (E) universe[(lastReturnedIndex << 6) - + Long.numberOfTrailingZeros(lastReturned)]; + return (E) universe[(lastReturnedIndex << 5) + + Integer.numberOfTrailingZeros(lastReturned)]; } @Override public void remove() { if (lastReturned == 0) throw new IllegalStateException(); - final long oldElements = elements[lastReturnedIndex]; + final int oldElements = elements[lastReturnedIndex]; elements[lastReturnedIndex] &= ~lastReturned; if (oldElements != elements[lastReturnedIndex]) { size--; @@ -158,7 +165,8 @@ public void remove() { * * @return the number of elements in this set */ - public int size() { + @Override + public int size() { return size; } @@ -167,7 +175,8 @@ public int size() { * * @return true if this set contains no elements */ - public boolean isEmpty() { + @Override + public boolean isEmpty() { return size == 0; } @@ -177,7 +186,8 @@ public boolean isEmpty() { * @param e element to be checked for containment in this collection * @return true if this set contains the specified element */ - public boolean contains(Object e) { + @Override + public boolean contains(Object e) { if (e == null) return false; Class eClass = e.getClass(); @@ -185,7 +195,7 @@ public boolean contains(Object e) { return false; int eOrdinal = ((Enum)e).ordinal(); - return (elements[eOrdinal >>> 6] & (1L << eOrdinal)) != 0; + return (elements[eOrdinal >>> 5] & (1 << eOrdinal)) != 0; } // Modification Operations @@ -198,14 +208,15 @@ public boolean contains(Object e) { * * @throws NullPointerException if e is null */ - public boolean add(E e) { + @Override + public boolean add(E e) { typeCheck(e); int eOrdinal = e.ordinal(); - int eWordNum = eOrdinal >>> 6; + int eWordNum = eOrdinal >>> 5; - long oldElements = elements[eWordNum]; - elements[eWordNum] |= (1L << eOrdinal); + int oldElements = elements[eWordNum]; + elements[eWordNum] |= (1 << eOrdinal); boolean result = (elements[eWordNum] != oldElements); if (result) size++; @@ -218,7 +229,8 @@ public boolean add(E e) { * @param e element to be removed from this set, if present * @return true if the set contained the specified element */ - public boolean remove(Object e) { + @Override + public boolean remove(Object e) { if (e == null) return false; Class eClass = e.getClass(); @@ -227,8 +239,8 @@ public boolean remove(Object e) { int eOrdinal = ((Enum)e).ordinal(); int eWordNum = eOrdinal >>> 6; - long oldElements = elements[eWordNum]; - elements[eWordNum] &= ~(1L << eOrdinal); + int oldElements = elements[eWordNum]; + elements[eWordNum] &= ~(1 << eOrdinal); boolean result = (elements[eWordNum] != oldElements); if (result) size--; @@ -246,7 +258,8 @@ public boolean remove(Object e) { * in the specified collection * @throws NullPointerException if the specified collection is null */ - public boolean containsAll(Collection c) { + @Override + public boolean containsAll(Collection c) { if (!(c instanceof JumboEnumSet)) return super.containsAll(c); @@ -268,7 +281,8 @@ public boolean containsAll(Collection c) { * @throws NullPointerException if the specified collection or any of * its elements are null */ - public boolean addAll(Collection c) { + @Override + public boolean addAll(Collection c) { if (!(c instanceof JumboEnumSet)) return super.addAll(c); @@ -294,7 +308,8 @@ public boolean addAll(Collection c) { * @return true if this set changed as a result of the call * @throws NullPointerException if the specified collection is null */ - public boolean removeAll(Collection c) { + @Override + public boolean removeAll(Collection c) { if (!(c instanceof JumboEnumSet)) return super.removeAll(c); @@ -315,7 +330,8 @@ public boolean removeAll(Collection c) { * @return true if this set changed as a result of the call * @throws NullPointerException if the specified collection is null */ - public boolean retainAll(Collection c) { + @Override + public boolean retainAll(Collection c) { if (!(c instanceof JumboEnumSet)) return super.retainAll(c); @@ -334,7 +350,8 @@ public boolean retainAll(Collection c) { /** * Removes all of the elements from this set. */ - public void clear() { + @Override + public void clear() { Arrays.fill(elements, 0); size = 0; } @@ -348,7 +365,8 @@ public void clear() { * @param o object to be compared for equality with this set * @return true if the specified object is equal to this set */ - public boolean equals(Object o) { + @Override + public boolean equals(Object o) { if (!(o instanceof JumboEnumSet)) return super.equals(o); @@ -365,13 +383,14 @@ public boolean equals(Object o) { private boolean recalculateSize() { int oldSize = size; size = 0; - for (long elt : elements) + for (int elt : elements) size += Long.bitCount(elt); return size != oldSize; } - public EnumSet clone() { + @Override + public EnumSet clone() { JumboEnumSet result = (JumboEnumSet) super.clone(); result.elements = result.elements.clone(); return result; diff --git a/sources/net.sf.j2s.java.core/src/java/util/Map.java b/sources/net.sf.j2s.java.core/src/java/util/Map.java index 19840b268..94028c439 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/Map.java +++ b/sources/net.sf.j2s.java.core/src/java/util/Map.java @@ -381,6 +381,7 @@ interface Entry { * required to, throw this exception if the entry has been * removed from the backing map. */ + @Deprecated K getKey(); /** diff --git a/sources/net.sf.j2s.java.core/src/java/util/RegularEnumSet.java b/sources/net.sf.j2s.java.core/src/java/util/RegularEnumSet.java index 31a63f15c..dbc05f5ee 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/RegularEnumSet.java +++ b/sources/net.sf.j2s.java.core/src/java/util/RegularEnumSet.java @@ -26,38 +26,43 @@ package java.util; /** + * SwingJS switched to 32-bit from 64-bit. + * * Private implementation class for EnumSet, for "regular sized" enum types - * (i.e., those with 64 or fewer enum constants). + * (i.e., those with 32 or fewer enum constants). * * @author Josh Bloch * @since 1.5 * @serial exclude */ +@SuppressWarnings("serial") class RegularEnumSet> extends EnumSet { - private static final long serialVersionUID = 3411599620347842686L; /** * Bit vector representation of this set. The 2^k bit indicates the * presence of universe[k] in this set. */ - private long elements = 0L; + private int elements = 0; RegularEnumSet(ClasselementType, Enum[] universe) { super(elementType, universe); } - void addRange(E from, E to) { - elements = (-1L >>> (from.ordinal() - to.ordinal() - 1)) << from.ordinal(); + @Override + void addRange(E from, E to) { + elements = (-1 >>> (from.ordinal() - to.ordinal() - 1)) << from.ordinal(); } - void addAll() { + @Override + void addAll() { if (universe.length != 0) - elements = -1L >>> -universe.length; + elements = -1 >>> -universe.length; } - void complement() { + @Override + void complement() { if (universe.length != 0) { elements = ~elements; - elements &= -1L >>> -universe.length; // Mask unused bits + elements &= -1 >>> -universe.length; // Mask unused bits } } @@ -71,41 +76,45 @@ void complement() { * * @return an iterator over the elements contained in this set */ - public Iterator iterator() { + @Override + public Iterator iterator() { return new EnumSetIterator<>(); } - private class EnumSetIterator> implements Iterator { + @SuppressWarnings("hiding") + private class EnumSetIterator> implements Iterator { /** * A bit vector representing the elements in the set not yet * returned by this iterator. */ - long unseen; + int unseen; /** * The bit representing the last element returned by this iterator * but not removed, or zero if no such element exists. */ - long lastReturned = 0; + int lastReturned = 0; EnumSetIterator() { unseen = elements; } - public boolean hasNext() { + @Override + public boolean hasNext() { return unseen != 0; } - @SuppressWarnings("unchecked") + @Override public E next() { if (unseen == 0) throw new NoSuchElementException(); lastReturned = unseen & -unseen; unseen -= lastReturned; - return (E) universe[Long.numberOfTrailingZeros(lastReturned)]; + return (E) universe[Integer.numberOfTrailingZeros(lastReturned)]; } - public void remove() { + @Override + public void remove() { if (lastReturned == 0) throw new IllegalStateException(); elements &= ~lastReturned; @@ -118,8 +127,9 @@ public void remove() { * * @return the number of elements in this set */ - public int size() { - return Long.bitCount(elements); + @Override + public int size() { + return Integer.bitCount(elements); } /** @@ -127,7 +137,8 @@ public int size() { * * @return true if this set contains no elements */ - public boolean isEmpty() { + @Override + public boolean isEmpty() { return elements == 0; } @@ -137,7 +148,8 @@ public boolean isEmpty() { * @param e element to be checked for containment in this collection * @return true if this set contains the specified element */ - public boolean contains(Object e) { + @Override + public boolean contains(Object e) { if (e == null) return false; Class eClass = e.getClass(); @@ -157,10 +169,11 @@ public boolean contains(Object e) { * * @throws NullPointerException if e is null */ - public boolean add(E e) { + @Override + public boolean add(E e) { typeCheck(e); - long oldElements = elements; + int oldElements = elements; elements |= (1L << ((Enum)e).ordinal()); return elements != oldElements; } @@ -171,14 +184,15 @@ public boolean add(E e) { * @param e element to be removed from this set, if present * @return true if the set contained the specified element */ - public boolean remove(Object e) { + @Override + public boolean remove(Object e) { if (e == null) return false; Class eClass = e.getClass(); if (eClass != elementType && eClass.getSuperclass() != elementType) return false; - long oldElements = elements; + int oldElements = elements; elements &= ~(1L << ((Enum)e).ordinal()); return elements != oldElements; } @@ -194,7 +208,8 @@ public boolean remove(Object e) { * in the specified collection * @throws NullPointerException if the specified collection is null */ - public boolean containsAll(Collection c) { + @Override + public boolean containsAll(Collection c) { if (!(c instanceof RegularEnumSet)) return super.containsAll(c); @@ -213,7 +228,8 @@ public boolean containsAll(Collection c) { * @throws NullPointerException if the specified collection or any * of its elements are null */ - public boolean addAll(Collection c) { + @Override + public boolean addAll(Collection c) { if (!(c instanceof RegularEnumSet)) return super.addAll(c); @@ -226,7 +242,7 @@ public boolean addAll(Collection c) { es.elementType + " != " + elementType); } - long oldElements = elements; + int oldElements = elements; elements |= es.elements; return elements != oldElements; } @@ -239,7 +255,8 @@ public boolean addAll(Collection c) { * @return true if this set changed as a result of the call * @throws NullPointerException if the specified collection is null */ - public boolean removeAll(Collection c) { + @Override + public boolean removeAll(Collection c) { if (!(c instanceof RegularEnumSet)) return super.removeAll(c); @@ -247,7 +264,7 @@ public boolean removeAll(Collection c) { if (es.elementType != elementType) return false; - long oldElements = elements; + int oldElements = elements; elements &= ~es.elements; return elements != oldElements; } @@ -260,7 +277,8 @@ public boolean removeAll(Collection c) { * @return true if this set changed as a result of the call * @throws NullPointerException if the specified collection is null */ - public boolean retainAll(Collection c) { + @Override + public boolean retainAll(Collection c) { if (!(c instanceof RegularEnumSet)) return super.retainAll(c); @@ -271,7 +289,7 @@ public boolean retainAll(Collection c) { return changed; } - long oldElements = elements; + int oldElements = elements; elements &= es.elements; return elements != oldElements; } @@ -279,7 +297,8 @@ public boolean retainAll(Collection c) { /** * Removes all of the elements from this set. */ - public void clear() { + @Override + public void clear() { elements = 0; } @@ -292,7 +311,8 @@ public void clear() { * @param o object to be compared for equality with this set * @return true if the specified object is equal to this set */ - public boolean equals(Object o) { + @Override + public boolean equals(Object o) { if (!(o instanceof RegularEnumSet)) return super.equals(o); diff --git a/sources/net.sf.j2s.java.core/src/java/util/concurrent/CompletableFuture.java b/sources/net.sf.j2s.java.core/src/java/util/concurrent/CompletableFuture.java index 955daabed..f4a111ea2 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/concurrent/CompletableFuture.java +++ b/sources/net.sf.j2s.java.core/src/java/util/concurrent/CompletableFuture.java @@ -930,6 +930,7 @@ final CompletableFuture tryFire(int mode) { } } + @Deprecated final boolean uniCompose( CompletableFuture a, Function> f, diff --git a/sources/net.sf.j2s.java.core/src/java/util/concurrent/ConcurrentSkipListMap.java b/sources/net.sf.j2s.java.core/src/java/util/concurrent/ConcurrentSkipListMap.java index 52085c3b7..fdf7f2643 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/concurrent/ConcurrentSkipListMap.java +++ b/sources/net.sf.j2s.java.core/src/java/util/concurrent/ConcurrentSkipListMap.java @@ -38,1394 +38,1399 @@ import java.util.concurrent.atomic.*; /** - * A scalable concurrent {@link ConcurrentNavigableMap} implementation. - * The map is sorted according to the {@linkplain Comparable natural - * ordering} of its keys, or by a {@link Comparator} provided at map - * creation time, depending on which constructor is used. - * - *

This class implements a concurrent variant of SkipLists providing - * expected average log(n) time cost for the - * containsKey, get, put and - * remove operations and their variants. Insertion, removal, - * update, and access operations safely execute concurrently by - * multiple threads. Iterators are weakly consistent, returning - * elements reflecting the state of the map at some point at or since - * the creation of the iterator. They do not throw {@link - * ConcurrentModificationException}, and may proceed concurrently with - * other operations. Ascending key ordered views and their iterators - * are faster than descending ones. - * - *

All Map.Entry pairs returned by methods in this class - * and its views represent snapshots of mappings at the time they were - * produced. They do not support the Entry.setValue - * method. (Note however that it is possible to change mappings in the - * associated map using put, putIfAbsent, or - * replace, depending on exactly which effect you need.) - * - *

Beware that, unlike in most collections, the size - * method is not a constant-time operation. Because of the - * asynchronous nature of these maps, determining the current number - * of elements requires a traversal of the elements. Additionally, - * the bulk operations putAll, equals, and - * clear are not guaranteed to be performed - * atomically. For example, an iterator operating concurrently with a - * putAll operation might view only some of the added - * elements. - * - *

This class and its views and iterators implement all of the - * optional methods of the {@link Map} and {@link Iterator} - * interfaces. Like most other concurrent collections, this class does - * not permit the use of null keys or values because some - * null return values cannot be reliably distinguished from the absence of - * elements. - * - *

This class is a member of the - * - * Java Collections Framework. - * - * @author Doug Lea - * @param the type of keys maintained by this map - * @param the type of mapped values - * @since 1.6 + * For SwingJS, we just use a standard LinkedHashMap */ -public class ConcurrentSkipListMap extends AbstractMap - implements ConcurrentNavigableMap, - Cloneable, - java.io.Serializable { - /* - * This class implements a tree-like two-dimensionally linked skip - * list in which the index levels are represented in separate - * nodes from the base nodes holding data. There are two reasons - * for taking this approach instead of the usual array-based - * structure: 1) Array based implementations seem to encounter - * more complexity and overhead 2) We can use cheaper algorithms - * for the heavily-traversed index lists than can be used for the - * base lists. Here's a picture of some of the basics for a - * possible list with 2 levels of index: - * - * Head nodes Index nodes - * +-+ right +-+ +-+ - * |2|---------------->| |--------------------->| |->null - * +-+ +-+ +-+ - * | down | | - * v v v - * +-+ +-+ +-+ +-+ +-+ +-+ - * |1|----------->| |->| |------>| |----------->| |------>| |->null - * +-+ +-+ +-+ +-+ +-+ +-+ - * v | | | | | - * Nodes next v v v v v - * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ - * | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null - * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ - * - * The base lists use a variant of the HM linked ordered set - * algorithm. See Tim Harris, "A pragmatic implementation of - * non-blocking linked lists" - * http://www.cl.cam.ac.uk/~tlh20/publications.html and Maged - * Michael "High Performance Dynamic Lock-Free Hash Tables and - * List-Based Sets" - * http://www.research.ibm.com/people/m/michael/pubs.htm. The - * basic idea in these lists is to mark the "next" pointers of - * deleted nodes when deleting to avoid conflicts with concurrent - * insertions, and when traversing to keep track of triples - * (predecessor, node, successor) in order to detect when and how - * to unlink these deleted nodes. - * - * Rather than using mark-bits to mark list deletions (which can - * be slow and space-intensive using AtomicMarkedReference), nodes - * use direct CAS'able next pointers. On deletion, instead of - * marking a pointer, they splice in another node that can be - * thought of as standing for a marked pointer (indicating this by - * using otherwise impossible field values). Using plain nodes - * acts roughly like "boxed" implementations of marked pointers, - * but uses new nodes only when nodes are deleted, not for every - * link. This requires less space and supports faster - * traversal. Even if marked references were better supported by - * JVMs, traversal using this technique might still be faster - * because any search need only read ahead one more node than - * otherwise required (to check for trailing marker) rather than - * unmasking mark bits or whatever on each read. - * - * This approach maintains the essential property needed in the HM - * algorithm of changing the next-pointer of a deleted node so - * that any other CAS of it will fail, but implements the idea by - * changing the pointer to point to a different node, not by - * marking it. While it would be possible to further squeeze - * space by defining marker nodes not to have key/value fields, it - * isn't worth the extra type-testing overhead. The deletion - * markers are rarely encountered during traversal and are - * normally quickly garbage collected. (Note that this technique - * would not work well in systems without garbage collection.) - * - * In addition to using deletion markers, the lists also use - * nullness of value fields to indicate deletion, in a style - * similar to typical lazy-deletion schemes. If a node's value is - * null, then it is considered logically deleted and ignored even - * though it is still reachable. This maintains proper control of - * concurrent replace vs delete operations -- an attempted replace - * must fail if a delete beat it by nulling field, and a delete - * must return the last non-null value held in the field. (Note: - * Null, rather than some special marker, is used for value fields - * here because it just so happens to mesh with the Map API - * requirement that method get returns null if there is no - * mapping, which allows nodes to remain concurrently readable - * even when deleted. Using any other marker value here would be - * messy at best.) - * - * Here's the sequence of events for a deletion of node n with - * predecessor b and successor f, initially: - * - * +------+ +------+ +------+ - * ... | b |------>| n |----->| f | ... - * +------+ +------+ +------+ - * - * 1. CAS n's value field from non-null to null. - * From this point on, no public operations encountering - * the node consider this mapping to exist. However, other - * ongoing insertions and deletions might still modify - * n's next pointer. - * - * 2. CAS n's next pointer to point to a new marker node. - * From this point on, no other nodes can be appended to n. - * which avoids deletion errors in CAS-based linked lists. - * - * +------+ +------+ +------+ +------+ - * ... | b |------>| n |----->|marker|------>| f | ... - * +------+ +------+ +------+ +------+ - * - * 3. CAS b's next pointer over both n and its marker. - * From this point on, no new traversals will encounter n, - * and it can eventually be GCed. - * +------+ +------+ - * ... | b |----------------------------------->| f | ... - * +------+ +------+ - * - * A failure at step 1 leads to simple retry due to a lost race - * with another operation. Steps 2-3 can fail because some other - * thread noticed during a traversal a node with null value and - * helped out by marking and/or unlinking. This helping-out - * ensures that no thread can become stuck waiting for progress of - * the deleting thread. The use of marker nodes slightly - * complicates helping-out code because traversals must track - * consistent reads of up to four nodes (b, n, marker, f), not - * just (b, n, f), although the next field of a marker is - * immutable, and once a next field is CAS'ed to point to a - * marker, it never again changes, so this requires less care. - * - * Skip lists add indexing to this scheme, so that the base-level - * traversals start close to the locations being found, inserted - * or deleted -- usually base level traversals only traverse a few - * nodes. This doesn't change the basic algorithm except for the - * need to make sure base traversals start at predecessors (here, - * b) that are not (structurally) deleted, otherwise retrying - * after processing the deletion. - * - * Index levels are maintained as lists with volatile next fields, - * using CAS to link and unlink. Races are allowed in index-list - * operations that can (rarely) fail to link in a new index node - * or delete one. (We can't do this of course for data nodes.) - * However, even when this happens, the index lists remain sorted, - * so correctly serve as indices. This can impact performance, - * but since skip lists are probabilistic anyway, the net result - * is that under contention, the effective "p" value may be lower - * than its nominal value. And race windows are kept small enough - * that in practice these failures are rare, even under a lot of - * contention. - * - * The fact that retries (for both base and index lists) are - * relatively cheap due to indexing allows some minor - * simplifications of retry logic. Traversal restarts are - * performed after most "helping-out" CASes. This isn't always - * strictly necessary, but the implicit backoffs tend to help - * reduce other downstream failed CAS's enough to outweigh restart - * cost. This worsens the worst case, but seems to improve even - * highly contended cases. - * - * Unlike most skip-list implementations, index insertion and - * deletion here require a separate traversal pass occuring after - * the base-level action, to add or remove index nodes. This adds - * to single-threaded overhead, but improves contended - * multithreaded performance by narrowing interference windows, - * and allows deletion to ensure that all index nodes will be made - * unreachable upon return from a public remove operation, thus - * avoiding unwanted garbage retention. This is more important - * here than in some other data structures because we cannot null - * out node fields referencing user keys since they might still be - * read by other ongoing traversals. - * - * Indexing uses skip list parameters that maintain good search - * performance while using sparser-than-usual indices: The - * hardwired parameters k=1, p=0.5 (see method randomLevel) mean - * that about one-quarter of the nodes have indices. Of those that - * do, half have one level, a quarter have two, and so on (see - * Pugh's Skip List Cookbook, sec 3.4). The expected total space - * requirement for a map is slightly less than for the current - * implementation of java.util.TreeMap. - * - * Changing the level of the index (i.e, the height of the - * tree-like structure) also uses CAS. The head index has initial - * level/height of one. Creation of an index with height greater - * than the current level adds a level to the head index by - * CAS'ing on a new top-most head. To maintain good performance - * after a lot of removals, deletion methods heuristically try to - * reduce the height if the topmost levels appear to be empty. - * This may encounter races in which it possible (but rare) to - * reduce and "lose" a level just as it is about to contain an - * index (that will then never be encountered). This does no - * structural harm, and in practice appears to be a better option - * than allowing unrestrained growth of levels. - * - * The code for all this is more verbose than you'd like. Most - * operations entail locating an element (or position to insert an - * element). The code to do this can't be nicely factored out - * because subsequent uses require a snapshot of predecessor - * and/or successor and/or value fields which can't be returned - * all at once, at least not without creating yet another object - * to hold them -- creating such little objects is an especially - * bad idea for basic internal search operations because it adds - * to GC overhead. (This is one of the few times I've wished Java - * had macros.) Instead, some traversal code is interleaved within - * insertion and removal operations. The control logic to handle - * all the retry conditions is sometimes twisty. Most search is - * broken into 2 parts. findPredecessor() searches index nodes - * only, returning a base-level predecessor of the key. findNode() - * finishes out the base-level search. Even with this factoring, - * there is a fair amount of near-duplication of code to handle - * variants. - * - * For explanation of algorithms sharing at least a couple of - * features with this one, see Mikhail Fomitchev's thesis - * (http://www.cs.yorku.ca/~mikhail/), Keir Fraser's thesis - * (http://www.cl.cam.ac.uk/users/kaf24/), and Hakan Sundell's - * thesis (http://www.cs.chalmers.se/~phs/). - * - * Given the use of tree-like index nodes, you might wonder why - * this doesn't use some kind of search tree instead, which would - * support somewhat faster search operations. The reason is that - * there are no known efficient lock-free insertion and deletion - * algorithms for search trees. The immutability of the "down" - * links of index nodes (as opposed to mutable "left" fields in - * true trees) makes this tractable using only CAS operations. - * - * Notation guide for local variables - * Node: b, n, f for predecessor, node, successor - * Index: q, r, d for index node, right, down. - * t for another index node - * Head: h - * Levels: j - * Keys: k, key - * Values: v, value - * Comparisons: c - */ - - private static final long serialVersionUID = -8627078645895051609L; - - /** - * Generates the initial random seed for the cheaper per-instance - * random number generators used in randomLevel. - */ - private static final Random seedGenerator = new Random(); - - /** - * Special value used to identify base-level header - */ - private static final Object BASE_HEADER = new Object(); - - /** - * The topmost head index of the skiplist. - */ - private transient volatile HeadIndex head; - - /** - * The comparator used to maintain order in this map, or null - * if using natural ordering. - * @serial - */ - private final Comparator comparator; - - /** - * Seed for simple random number generator. Not volatile since it - * doesn't matter too much if different threads don't see updates. - */ - private transient int randomSeed; - - /** Lazily initialized key set */ - private transient KeySet keySet; - /** Lazily initialized entry set */ - private transient EntrySet entrySet; - /** Lazily initialized values collection */ - private transient Values values; - /** Lazily initialized descending key set */ - private transient ConcurrentNavigableMap descendingMap; - - /** - * Initializes or resets state. Needed by constructors, clone, - * clear, readObject. and ConcurrentSkipListSet.clone. - * (Note that comparator must be separately initialized.) - */ - final void initialize() { - keySet = null; - entrySet = null; - values = null; - descendingMap = null; - randomSeed = seedGenerator.nextInt() | 0x0100; // ensure nonzero - head = new HeadIndex(new Node(null, BASE_HEADER, null), - null, null, 1); - } - - /** Updater for casHead */ - private static final - AtomicReferenceFieldUpdater - headUpdater = AtomicReferenceFieldUpdater.newUpdater - (ConcurrentSkipListMap.class, HeadIndex.class, "head"); - - /** - * compareAndSet head node - */ - private boolean casHead(HeadIndex cmp, HeadIndex val) { - return headUpdater.compareAndSet(this, cmp, val); - } - - /* ---------------- Nodes -------------- */ - - /** - * Nodes hold keys and values, and are singly linked in sorted - * order, possibly with some intervening marker nodes. The list is - * headed by a dummy node accessible as head.node. The value field - * is declared only as Object because it takes special non-V - * values for marker and header nodes. - */ - static final class Node { - final K key; - volatile Object value; - volatile Node next_; - - /** - * Creates a new regular node. - */ - Node(K key, Object value, Node next) { - this.key = key; - this.value = value; - this.next_ = next; - } - - /** - * Creates a new marker node. A marker is distinguished by - * having its value field point to itself. Marker nodes also - * have null keys, a fact that is exploited in a few places, - * but this doesn't distinguish markers from the base-level - * header node (head.node), which also has a null key. - */ - Node(Node next) { - this.key = null; - this.value = this; - this.next_ = next; - } - - /** Updater for casNext */ - static final AtomicReferenceFieldUpdater - nextUpdater = AtomicReferenceFieldUpdater.newUpdater - (Node.class, Node.class, "next"); - - /** Updater for casValue */ - static final AtomicReferenceFieldUpdater - valueUpdater = AtomicReferenceFieldUpdater.newUpdater - (Node.class, Object.class, "value"); - - /** - * compareAndSet value field - */ - boolean casValue(Object cmp, Object val) { - return valueUpdater.compareAndSet(this, cmp, val); - } - - /** - * compareAndSet next field - */ - boolean casNext(Node cmp, Node val) { - return nextUpdater.compareAndSet(this, cmp, val); - } - - /** - * Returns true if this node is a marker. This method isn't - * actually called in any current code checking for markers - * because callers will have already read value field and need - * to use that read (not another done here) and so directly - * test if value points to node. - * @param n a possibly null reference to a node - * @return true if this node is a marker node - */ - boolean isMarker() { - return value == this; - } - - /** - * Returns true if this node is the header of base-level list. - * @return true if this node is header node - */ - boolean isBaseHeader() { - return value == BASE_HEADER; - } - - /** - * Tries to append a deletion marker to this node. - * @param f the assumed current successor of this node - * @return true if successful - */ - boolean appendMarker(Node f) { - return casNext(f, new Node(f)); - } - - /** - * Helps out a deletion by appending marker or unlinking from - * predecessor. This is called during traversals when value - * field seen to be null. - * @param b predecessor - * @param f successor - */ - void helpDelete(Node b, Node f) { - /* - * Rechecking links and then doing only one of the - * help-out stages per call tends to minimize CAS - * interference among helping threads. - */ - if (f == next_ && this == b.next_) { - if (f == null || f.value != f) // not already marked - appendMarker(f); - else - b.casNext(this, f.next_); - } - } - - /** - * Returns value if this node contains a valid key-value pair, - * else null. - * @return this node's value if it isn't a marker or header or - * is deleted, else null. - */ - V getValidValue() { - Object v = value; - if (v == this || v == BASE_HEADER) - return null; - return (V)v; - } - - /** - * Creates and returns a new SimpleImmutableEntry holding current - * mapping if this node holds a valid value, else null. - * @return new entry or null - */ - AbstractMap.SimpleImmutableEntry createSnapshot() { - V v = getValidValue(); - if (v == null) - return null; - return new AbstractMap.SimpleImmutableEntry(key, v); - } - } - - /* ---------------- Indexing -------------- */ - - /** - * Index nodes represent the levels of the skip list. Note that - * even though both Nodes and Indexes have forward-pointing - * fields, they have different types and are handled in different - * ways, that can't nicely be captured by placing field in a - * shared abstract class. - */ - static class Index { - final Node node; - final Index down; - volatile Index right; - - /** - * Creates index node with given values. - */ - Index(Node node, Index down, Index right) { - this.node = node; - this.down = down; - this.right = right; - } - - /** Updater for casRight */ - static final AtomicReferenceFieldUpdater - rightUpdater = AtomicReferenceFieldUpdater.newUpdater - (Index.class, Index.class, "right"); - - /** - * compareAndSet right field - */ - final boolean casRight(Index cmp, Index val) { - return rightUpdater.compareAndSet(this, cmp, val); - } - - /** - * Returns true if the node this indexes has been deleted. - * @return true if indexed node is known to be deleted - */ - final boolean indexesDeletedNode() { - return node.value == null; - } - - /** - * Tries to CAS newSucc as successor. To minimize races with - * unlink that may lose this index node, if the node being - * indexed is known to be deleted, it doesn't try to link in. - * @param succ the expected current successor - * @param newSucc the new successor - * @return true if successful - */ - final boolean link(Index succ, Index newSucc) { - Node n = node; - newSucc.right = succ; - return n.value != null && casRight(succ, newSucc); - } - - /** - * Tries to CAS right field to skip over apparent successor - * succ. Fails (forcing a retraversal by caller) if this node - * is known to be deleted. - * @param succ the expected current successor - * @return true if successful - */ - final boolean unlink(Index succ) { - return !indexesDeletedNode() && casRight(succ, succ.right); - } - } - - /* ---------------- Head nodes -------------- */ - - /** - * Nodes heading each level keep track of their level. - */ - static final class HeadIndex extends Index { - final int level; - HeadIndex(Node node, Index down, Index right, int level) { - super(node, down, right); - this.level = level; - } - } - - /* ---------------- Comparison utilities -------------- */ - - /** - * Represents a key with a comparator as a Comparable. - * - * Because most sorted collections seem to use natural ordering on - * Comparables (Strings, Integers, etc), most internal methods are - * geared to use them. This is generally faster than checking - * per-comparison whether to use comparator or comparable because - * it doesn't require a (Comparable) cast for each comparison. - * (Optimizers can only sometimes remove such redundant checks - * themselves.) When Comparators are used, - * ComparableUsingComparators are created so that they act in the - * same way as natural orderings. This penalizes use of - * Comparators vs Comparables, which seems like the right - * tradeoff. - */ - static final class ComparableUsingComparator implements Comparable { - final K actualKey; - final Comparator cmp; - ComparableUsingComparator(K key, Comparator cmp) { - this.actualKey = key; - this.cmp = cmp; - } - public int compareTo(K k2) { - return cmp.compare(actualKey, k2); - } - } - - /** - * If using comparator, return a ComparableUsingComparator, else - * cast key as Comparable, which may cause ClassCastException, - * which is propagated back to caller. - */ - private Comparable comparable(Object key) throws ClassCastException { - if (key == null) - throw new NullPointerException(); - if (comparator != null) - return new ComparableUsingComparator((K)key, comparator); - else - return (Comparable)key; - } - - /** - * Compares using comparator or natural ordering. Used when the - * ComparableUsingComparator approach doesn't apply. - */ - int compare(K k1, K k2) throws ClassCastException { - Comparator cmp = comparator; - if (cmp != null) - return cmp.compare(k1, k2); - else - return ((Comparable)k1).compareTo(k2); - } - - /** - * Returns true if given key greater than or equal to least and - * strictly less than fence, bypassing either test if least or - * fence are null. Needed mainly in submap operations. - */ - boolean inHalfOpenRange(K key, K least, K fence) { - if (key == null) - throw new NullPointerException(); - return ((least == null || compare(key, least) >= 0) && - (fence == null || compare(key, fence) < 0)); - } - - /** - * Returns true if given key greater than or equal to least and less - * or equal to fence. Needed mainly in submap operations. - */ - boolean inOpenRange(K key, K least, K fence) { - if (key == null) - throw new NullPointerException(); - return ((least == null || compare(key, least) >= 0) && - (fence == null || compare(key, fence) <= 0)); - } - - /* ---------------- Traversal -------------- */ - - /** - * Returns a base-level node with key strictly less than given key, - * or the base-level header if there is no such node. Also - * unlinks indexes to deleted nodes found along the way. Callers - * rely on this side-effect of clearing indices to deleted nodes. - * @param key the key - * @return a predecessor of key - */ - private Node findPredecessor(Comparable key) { - if (key == null) - throw new NullPointerException(); // don't postpone errors - for (;;) { - Index q = head; - Index r = q.right; - for (;;) { - if (r != null) { - Node n = r.node; - K k = n.key; - if (n.value == null) { - if (!q.unlink(r)) - break; // restart - r = q.right; // reread r - continue; - } - if (key.compareTo(k) > 0) { - q = r; - r = r.right; - continue; - } - } - Index d = q.down; - if (d != null) { - q = d; - r = d.right; - } else - return q.node; - } - } - } - - /** - * Returns node holding key or null if no such, clearing out any - * deleted nodes seen along the way. Repeatedly traverses at - * base-level looking for key starting at predecessor returned - * from findPredecessor, processing base-level deletions as - * encountered. Some callers rely on this side-effect of clearing - * deleted nodes. - * - * Restarts occur, at traversal step centered on node n, if: - * - * (1) After reading n's next field, n is no longer assumed - * predecessor b's current successor, which means that - * we don't have a consistent 3-node snapshot and so cannot - * unlink any subsequent deleted nodes encountered. - * - * (2) n's value field is null, indicating n is deleted, in - * which case we help out an ongoing structural deletion - * before retrying. Even though there are cases where such - * unlinking doesn't require restart, they aren't sorted out - * here because doing so would not usually outweigh cost of - * restarting. - * - * (3) n is a marker or n's predecessor's value field is null, - * indicating (among other possibilities) that - * findPredecessor returned a deleted node. We can't unlink - * the node because we don't know its predecessor, so rely - * on another call to findPredecessor to notice and return - * some earlier predecessor, which it will do. This check is - * only strictly needed at beginning of loop, (and the - * b.value check isn't strictly needed at all) but is done - * each iteration to help avoid contention with other - * threads by callers that will fail to be able to change - * links, and so will retry anyway. - * - * The traversal loops in doPut, doRemove, and findNear all - * include the same three kinds of checks. And specialized - * versions appear in findFirst, and findLast and their - * variants. They can't easily share code because each uses the - * reads of fields held in locals occurring in the orders they - * were performed. - * - * @param key the key - * @return node holding key, or null if no such - */ - private Node findNode(Comparable key) { - for (;;) { - Node b = findPredecessor(key); - Node n = b.next_; - for (;;) { - if (n == null) - return null; - Node f = n.next_; - if (n != b.next_) // inconsistent read - break; - Object v = n.value; - if (v == null) { // n is deleted - n.helpDelete(b, f); - break; - } - if (v == n || b.value == null) // b is deleted - break; - int c = key.compareTo(n.key); - if (c == 0) - return n; - if (c < 0) - return null; - b = n; - n = f; - } - } - } - - /** - * Specialized variant of findNode to perform Map.get. Does a weak - * traversal, not bothering to fix any deleted index nodes, - * returning early if it happens to see key in index, and passing - * over any deleted base nodes, falling back to getUsingFindNode - * only if it would otherwise return value from an ongoing - * deletion. Also uses "bound" to eliminate need for some - * comparisons (see Pugh Cookbook). Also folds uses of null checks - * and node-skipping because markers have null keys. - * @param okey the key - * @return the value, or null if absent - */ - private V doGet(Object okey) { - Comparable key = comparable(okey); - Node bound = null; - Index q = head; - Index r = q.right; - Node n; - K k; - int c; - for (;;) { - Index d; - // Traverse rights - if (r != null && (n = r.node) != bound && (k = n.key) != null) { - if ((c = key.compareTo(k)) > 0) { - q = r; - r = r.right; - continue; - } else if (c == 0) { - Object v = n.value; - return (v != null)? (V)v : getUsingFindNode(key); - } else - bound = n; - } - - // Traverse down - if ((d = q.down) != null) { - q = d; - r = d.right; - } else - break; - } - - // Traverse nexts - for (n = q.node.next_; n != null; n = n.next_) { - if ((k = n.key) != null) { - if ((c = key.compareTo(k)) == 0) { - Object v = n.value; - return (v != null)? (V)v : getUsingFindNode(key); - } else if (c < 0) - break; - } - } - return null; - } - - /** - * Performs map.get via findNode. Used as a backup if doGet - * encounters an in-progress deletion. - * @param key the key - * @return the value, or null if absent - */ - private V getUsingFindNode(Comparable key) { - /* - * Loop needed here and elsewhere in case value field goes - * null just as it is about to be returned, in which case we - * lost a race with a deletion, so must retry. - */ - for (;;) { - Node n = findNode(key); - if (n == null) - return null; - Object v = n.value; - if (v != null) - return (V)v; - } - } - - /* ---------------- Insertion -------------- */ - - /** - * Main insertion method. Adds element if not present, or - * replaces value if present and onlyIfAbsent is false. - * @param kkey the key - * @param value the value that must be associated with key - * @param onlyIfAbsent if should not insert if already present - * @return the old value, or null if newly inserted - */ - private V doPut(K kkey, V value, boolean onlyIfAbsent) { - Comparable key = comparable(kkey); - for (;;) { - Node b = findPredecessor(key); - Node n = b.next_; - for (;;) { - if (n != null) { - Node f = n.next_; - if (n != b.next_) // inconsistent read - break;; - Object v = n.value; - if (v == null) { // n is deleted - n.helpDelete(b, f); - break; - } - if (v == n || b.value == null) // b is deleted - break; - int c = key.compareTo(n.key); - if (c > 0) { - b = n; - n = f; - continue; - } - if (c == 0) { - if (onlyIfAbsent || n.casValue(v, value)) - return (V)v; - else - break; // restart if lost race to replace value - } - // else c < 0; fall through - } - - Node z = new Node(kkey, value, n); - if (!b.casNext(n, z)) - break; // restart if lost race to append to b - int level = randomLevel(); - if (level > 0) - insertIndex(z, level); - return null; - } - } - } - - /** - * Returns a random level for inserting a new node. - * Hardwired to k=1, p=0.5, max 31 (see above and - * Pugh's "Skip List Cookbook", sec 3.4). - * - * This uses the simplest of the generators described in George - * Marsaglia's "Xorshift RNGs" paper. This is not a high-quality - * generator but is acceptable here. - */ - private int randomLevel() { - int x = randomSeed; - x ^= x << 13; - x ^= x >>> 17; - randomSeed = x ^= x << 5; - if ((x & 0x8001) != 0) // test highest and lowest bits - return 0; - int level = 1; - while (((x >>>= 1) & 1) != 0) ++level; - return level; - } - - /** - * Creates and adds index nodes for the given node. - * @param z the node - * @param level the level of the index - */ - private void insertIndex(Node z, int level) { - HeadIndex h = head; - int max = h.level; - - if (level <= max) { - Index idx = null; - for (int i = 1; i <= level; ++i) - idx = new Index(z, idx, null); - addIndex(idx, h, level); - - } else { // Add a new level - /* - * To reduce interference by other threads checking for - * empty levels in tryReduceLevel, new levels are added - * with initialized right pointers. Which in turn requires - * keeping levels in an array to access them while - * creating new head index nodes from the opposite - * direction. - */ - level = max + 1; - Index[] idxs = (Index[])new Index[level+1]; - Index idx = null; - for (int i = 1; i <= level; ++i) - idxs[i] = idx = new Index(z, idx, null); - - HeadIndex oldh; - int k; - for (;;) { - oldh = head; - int oldLevel = oldh.level; - if (level <= oldLevel) { // lost race to add level - k = level; - break; - } - HeadIndex newh = oldh; - Node oldbase = oldh.node; - for (int j = oldLevel+1; j <= level; ++j) - newh = new HeadIndex(oldbase, newh, idxs[j], j); - if (casHead(oldh, newh)) { - k = oldLevel; - break; - } - } - addIndex(idxs[k], oldh, k); - } - } - - /** - * Adds given index nodes from given level down to 1. - * @param idx the topmost index node being inserted - * @param h the value of head to use to insert. This must be - * snapshotted by callers to provide correct insertion level - * @param indexLevel the level of the index - */ - private void addIndex(Index idx, HeadIndex h, int indexLevel) { - // Track next level to insert in case of retries - int insertionLevel = indexLevel; - Comparable key = comparable(idx.node.key); - if (key == null) throw new NullPointerException(); - - // Similar to findPredecessor, but adding index nodes along - // path to key. - for (;;) { - int j = h.level; - Index q = h; - Index r = q.right; - Index t = idx; - for (;;) { - if (r != null) { - Node n = r.node; - // compare before deletion check avoids needing recheck - int c = key.compareTo(n.key); - if (n.value == null) { - if (!q.unlink(r)) - break; - r = q.right; - continue; - } - if (c > 0) { - q = r; - r = r.right; - continue; - } - } - - if (j == insertionLevel) { - // Don't insert index if node already deleted - if (t.indexesDeletedNode()) { - findNode(key); // cleans up - return; - } - if (!q.link(r, t)) - break; // restart - if (--insertionLevel == 0) { - // need final deletion check before return - if (t.indexesDeletedNode()) - findNode(key); - return; - } - } - - if (--j >= insertionLevel && j < indexLevel) - t = t.down; - q = q.down; - r = q.right; - } - } - } - - /* ---------------- Deletion -------------- */ - - /** - * Main deletion method. Locates node, nulls value, appends a - * deletion marker, unlinks predecessor, removes associated index - * nodes, and possibly reduces head index level. - * - * Index nodes are cleared out simply by calling findPredecessor. - * which unlinks indexes to deleted nodes found along path to key, - * which will include the indexes to this node. This is done - * unconditionally. We can't check beforehand whether there are - * index nodes because it might be the case that some or all - * indexes hadn't been inserted yet for this node during initial - * search for it, and we'd like to ensure lack of garbage - * retention, so must call to be sure. - * - * @param okey the key - * @param value if non-null, the value that must be - * associated with key - * @return the node, or null if not found - */ - final V doRemove(Object okey, Object value) { - Comparable key = comparable(okey); - for (;;) { - Node b = findPredecessor(key); - Node n = b.next_; - for (;;) { - if (n == null) - return null; - Node f = n.next_; - if (n != b.next_) // inconsistent read - break; - Object v = n.value; - if (v == null) { // n is deleted - n.helpDelete(b, f); - break; - } - if (v == n || b.value == null) // b is deleted - break; - int c = key.compareTo(n.key); - if (c < 0) - return null; - if (c > 0) { - b = n; - n = f; - continue; - } - if (value != null && !value.equals(v)) - return null; - if (!n.casValue(v, null)) - break; - if (!n.appendMarker(f) || !b.casNext(n, f)) - findNode(key); // Retry via findNode - else { - findPredecessor(key); // Clean index - if (head.right == null) - tryReduceLevel(); - } - return (V)v; - } - } - } - - /** - * Possibly reduce head level if it has no nodes. This method can - * (rarely) make mistakes, in which case levels can disappear even - * though they are about to contain index nodes. This impacts - * performance, not correctness. To minimize mistakes as well as - * to reduce hysteresis, the level is reduced by one only if the - * topmost three levels look empty. Also, if the removed level - * looks non-empty after CAS, we try to change it back quick - * before anyone notices our mistake! (This trick works pretty - * well because this method will practically never make mistakes - * unless current thread stalls immediately before first CAS, in - * which case it is very unlikely to stall again immediately - * afterwards, so will recover.) - * - * We put up with all this rather than just let levels grow - * because otherwise, even a small map that has undergone a large - * number of insertions and removals will have a lot of levels, - * slowing down access more than would an occasional unwanted - * reduction. - */ - private void tryReduceLevel() { - HeadIndex h = head; - HeadIndex d; - HeadIndex e; - if (h.level > 3 && - (d = (HeadIndex)h.down) != null && - (e = (HeadIndex)d.down) != null && - e.right == null && - d.right == null && - h.right == null && - casHead(h, d) && // try to set - h.right != null) // recheck - casHead(d, h); // try to backout - } - - /* ---------------- Finding and removing first element -------------- */ - - /** - * Specialized variant of findNode to get first valid node. - * @return first node or null if empty - */ - Node findFirst() { - for (;;) { - Node b = head.node; - Node n = b.next_; - if (n == null) - return null; - if (n.value != null) - return n; - n.helpDelete(b, n.next_); - } - } - - /** - * Removes first entry; returns its snapshot. - * @return null if empty, else snapshot of first entry - */ - Map.Entry doRemoveFirstEntry() { - for (;;) { - Node b = head.node; - Node n = b.next_; - if (n == null) - return null; - Node f = n.next_; - if (n != b.next_) - continue; - Object v = n.value; - if (v == null) { - n.helpDelete(b, f); - continue; - } - if (!n.casValue(v, null)) - continue; - if (!n.appendMarker(f) || !b.casNext(n, f)) - findFirst(); // retry - clearIndexToFirst(); - return new AbstractMap.SimpleImmutableEntry(n.key, (V)v); - } - } - - /** - * Clears out index nodes associated with deleted first entry. - */ - private void clearIndexToFirst() { - for (;;) { - Index q = head; - for (;;) { - Index r = q.right; - if (r != null && r.indexesDeletedNode() && !q.unlink(r)) - break; - if ((q = q.down) == null) { - if (head.right == null) - tryReduceLevel(); - return; - } - } - } - } - - - /* ---------------- Finding and removing last element -------------- */ - - /** - * Specialized version of find to get last valid node. - * @return last node or null if empty - */ - Node findLast() { - /* - * findPredecessor can't be used to traverse index level - * because this doesn't use comparisons. So traversals of - * both levels are folded together. - */ - Index q = head; - for (;;) { - Index d, r; - if ((r = q.right) != null) { - if (r.indexesDeletedNode()) { - q.unlink(r); - q = head; // restart - } - else - q = r; - } else if ((d = q.down) != null) { - q = d; - } else { - Node b = q.node; - Node n = b.next_; - for (;;) { - if (n == null) - return (b.isBaseHeader())? null : b; - Node f = n.next_; // inconsistent read - if (n != b.next_) - break; - Object v = n.value; - if (v == null) { // n is deleted - n.helpDelete(b, f); - break; - } - if (v == n || b.value == null) // b is deleted - break; - b = n; - n = f; - } - q = head; // restart - } - } - } - - /** - * Specialized variant of findPredecessor to get predecessor of last - * valid node. Needed when removing the last entry. It is possible - * that all successors of returned node will have been deleted upon - * return, in which case this method can be retried. - * @return likely predecessor of last node - */ - private Node findPredecessorOfLast() { - for (;;) { - Index q = head; - for (;;) { - Index d, r; - if ((r = q.right) != null) { - if (r.indexesDeletedNode()) { - q.unlink(r); - break; // must restart - } - // proceed as far across as possible without overshooting - if (r.node.next_ != null) { - q = r; - continue; - } - } - if ((d = q.down) != null) - q = d; - else - return q.node; - } - } - } - - /** - * Removes last entry; returns its snapshot. - * Specialized variant of doRemove. - * @return null if empty, else snapshot of last entry - */ - Map.Entry doRemoveLastEntry() { - for (;;) { - Node b = findPredecessorOfLast(); - Node n = b.next_; - if (n == null) { - if (b.isBaseHeader()) // empty - return null; - else - continue; // all b's successors are deleted; retry - } - for (;;) { - Node f = n.next_; - if (n != b.next_) // inconsistent read - break; - Object v = n.value; - if (v == null) { // n is deleted - n.helpDelete(b, f); - break; - } - if (v == n || b.value == null) // b is deleted - break; - if (f != null) { - b = n; - n = f; - continue; - } - if (!n.casValue(v, null)) - break; - K key = n.key; - Comparable ck = comparable(key); - if (!n.appendMarker(f) || !b.casNext(n, f)) - findNode(ck); // Retry via findNode - else { - findPredecessor(ck); // Clean index - if (head.right == null) - tryReduceLevel(); - } - return new AbstractMap.SimpleImmutableEntry(key, (V)v); - } - } - } - - /* ---------------- Relational operations -------------- */ - - // Control values OR'ed as arguments to findNear - - private static final int EQ = 1; - private static final int LT = 2; - private static final int GT = 0; // Actually checked as !LT - - /** - * Utility for ceiling, floor, lower, higher methods. - * @param kkey the key - * @param rel the relation -- OR'ed combination of EQ, LT, GT - * @return nearest node fitting relation, or null if no such - */ - Node findNear(K kkey, int rel) { - Comparable key = comparable(kkey); - for (;;) { - Node b = findPredecessor(key); - Node n = b.next_; - for (;;) { - if (n == null) - return ((rel & LT) == 0 || b.isBaseHeader())? null : b; - Node f = n.next_; - if (n != b.next_) // inconsistent read - break; - Object v = n.value; - if (v == null) { // n is deleted - n.helpDelete(b, f); - break; - } - if (v == n || b.value == null) // b is deleted - break; - int c = key.compareTo(n.key); - if ((c == 0 && (rel & EQ) != 0) || - (c < 0 && (rel & LT) == 0)) - return n; - if ( c <= 0 && (rel & LT) != 0) - return (b.isBaseHeader())? null : b; - b = n; - n = f; - } - } - } - - /** - * Returns SimpleImmutableEntry for results of findNear. - * @param key the key - * @param rel the relation -- OR'ed combination of EQ, LT, GT - * @return Entry fitting relation, or null if no such - */ - AbstractMap.SimpleImmutableEntry getNear(K key, int rel) { - for (;;) { - Node n = findNear(key, rel); - if (n == null) - return null; - AbstractMap.SimpleImmutableEntry e = n.createSnapshot(); - if (e != null) - return e; - } - } - - - /* ---------------- Constructors -------------- */ - +///** +// * A scalable concurrent {@link ConcurrentNavigableMap} implementation. +// * The map is sorted according to the {@linkplain Comparable natural +// * ordering} of its keys, or by a {@link Comparator} provided at map +// * creation time, depending on which constructor is used. +// * +// *

This class implements a concurrent variant of SkipLists providing +// * expected average log(n) time cost for the +// * containsKey, get, put and +// * remove operations and their variants. Insertion, removal, +// * update, and access operations safely execute concurrently by +// * multiple threads. Iterators are weakly consistent, returning +// * elements reflecting the state of the map at some point at or since +// * the creation of the iterator. They do not throw {@link +// * ConcurrentModificationException}, and may proceed concurrently with +// * other operations. Ascending key ordered views and their iterators +// * are faster than descending ones. +// * +// *

All Map.Entry pairs returned by methods in this class +// * and its views represent snapshots of mappings at the time they were +// * produced. They do not support the Entry.setValue +// * method. (Note however that it is possible to change mappings in the +// * associated map using put, putIfAbsent, or +// * replace, depending on exactly which effect you need.) +// * +// *

Beware that, unlike in most collections, the size +// * method is not a constant-time operation. Because of the +// * asynchronous nature of these maps, determining the current number +// * of elements requires a traversal of the elements. Additionally, +// * the bulk operations putAll, equals, and +// * clear are not guaranteed to be performed +// * atomically. For example, an iterator operating concurrently with a +// * putAll operation might view only some of the added +// * elements. +// * +// *

This class and its views and iterators implement all of the +// * optional methods of the {@link Map} and {@link Iterator} +// * interfaces. Like most other concurrent collections, this class does +// * not permit the use of null keys or values because some +// * null return values cannot be reliably distinguished from the absence of +// * elements. +// * +// *

This class is a member of the +// * +// * Java Collections Framework. +// * +// * @author Doug Lea +// * @param the type of keys maintained by this map +// * @param the type of mapped values +// * @since 1.6 +// */ +public class ConcurrentSkipListMap extends LinkedHashMap +//AbstractMap +// implements ConcurrentNavigableMap, +// Cloneable, +// java.io.Serializable + { +// /* +// * This class implements a tree-like two-dimensionally linked skip +// * list in which the index levels are represented in separate +// * nodes from the base nodes holding data. There are two reasons +// * for taking this approach instead of the usual array-based +// * structure: 1) Array based implementations seem to encounter +// * more complexity and overhead 2) We can use cheaper algorithms +// * for the heavily-traversed index lists than can be used for the +// * base lists. Here's a picture of some of the basics for a +// * possible list with 2 levels of index: +// * +// * Head nodes Index nodes +// * +-+ right +-+ +-+ +// * |2|---------------->| |--------------------->| |->null +// * +-+ +-+ +-+ +// * | down | | +// * v v v +// * +-+ +-+ +-+ +-+ +-+ +-+ +// * |1|----------->| |->| |------>| |----------->| |------>| |->null +// * +-+ +-+ +-+ +-+ +-+ +-+ +// * v | | | | | +// * Nodes next v v v v v +// * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +// * | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null +// * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +// * +// * The base lists use a variant of the HM linked ordered set +// * algorithm. See Tim Harris, "A pragmatic implementation of +// * non-blocking linked lists" +// * http://www.cl.cam.ac.uk/~tlh20/publications.html and Maged +// * Michael "High Performance Dynamic Lock-Free Hash Tables and +// * List-Based Sets" +// * http://www.research.ibm.com/people/m/michael/pubs.htm. The +// * basic idea in these lists is to mark the "next" pointers of +// * deleted nodes when deleting to avoid conflicts with concurrent +// * insertions, and when traversing to keep track of triples +// * (predecessor, node, successor) in order to detect when and how +// * to unlink these deleted nodes. +// * +// * Rather than using mark-bits to mark list deletions (which can +// * be slow and space-intensive using AtomicMarkedReference), nodes +// * use direct CAS'able next pointers. On deletion, instead of +// * marking a pointer, they splice in another node that can be +// * thought of as standing for a marked pointer (indicating this by +// * using otherwise impossible field values). Using plain nodes +// * acts roughly like "boxed" implementations of marked pointers, +// * but uses new nodes only when nodes are deleted, not for every +// * link. This requires less space and supports faster +// * traversal. Even if marked references were better supported by +// * JVMs, traversal using this technique might still be faster +// * because any search need only read ahead one more node than +// * otherwise required (to check for trailing marker) rather than +// * unmasking mark bits or whatever on each read. +// * +// * This approach maintains the essential property needed in the HM +// * algorithm of changing the next-pointer of a deleted node so +// * that any other CAS of it will fail, but implements the idea by +// * changing the pointer to point to a different node, not by +// * marking it. While it would be possible to further squeeze +// * space by defining marker nodes not to have key/value fields, it +// * isn't worth the extra type-testing overhead. The deletion +// * markers are rarely encountered during traversal and are +// * normally quickly garbage collected. (Note that this technique +// * would not work well in systems without garbage collection.) +// * +// * In addition to using deletion markers, the lists also use +// * nullness of value fields to indicate deletion, in a style +// * similar to typical lazy-deletion schemes. If a node's value is +// * null, then it is considered logically deleted and ignored even +// * though it is still reachable. This maintains proper control of +// * concurrent replace vs delete operations -- an attempted replace +// * must fail if a delete beat it by nulling field, and a delete +// * must return the last non-null value held in the field. (Note: +// * Null, rather than some special marker, is used for value fields +// * here because it just so happens to mesh with the Map API +// * requirement that method get returns null if there is no +// * mapping, which allows nodes to remain concurrently readable +// * even when deleted. Using any other marker value here would be +// * messy at best.) +// * +// * Here's the sequence of events for a deletion of node n with +// * predecessor b and successor f, initially: +// * +// * +------+ +------+ +------+ +// * ... | b |------>| n |----->| f | ... +// * +------+ +------+ +------+ +// * +// * 1. CAS n's value field from non-null to null. +// * From this point on, no public operations encountering +// * the node consider this mapping to exist. However, other +// * ongoing insertions and deletions might still modify +// * n's next pointer. +// * +// * 2. CAS n's next pointer to point to a new marker node. +// * From this point on, no other nodes can be appended to n. +// * which avoids deletion errors in CAS-based linked lists. +// * +// * +------+ +------+ +------+ +------+ +// * ... | b |------>| n |----->|marker|------>| f | ... +// * +------+ +------+ +------+ +------+ +// * +// * 3. CAS b's next pointer over both n and its marker. +// * From this point on, no new traversals will encounter n, +// * and it can eventually be GCed. +// * +------+ +------+ +// * ... | b |----------------------------------->| f | ... +// * +------+ +------+ +// * +// * A failure at step 1 leads to simple retry due to a lost race +// * with another operation. Steps 2-3 can fail because some other +// * thread noticed during a traversal a node with null value and +// * helped out by marking and/or unlinking. This helping-out +// * ensures that no thread can become stuck waiting for progress of +// * the deleting thread. The use of marker nodes slightly +// * complicates helping-out code because traversals must track +// * consistent reads of up to four nodes (b, n, marker, f), not +// * just (b, n, f), although the next field of a marker is +// * immutable, and once a next field is CAS'ed to point to a +// * marker, it never again changes, so this requires less care. +// * +// * Skip lists add indexing to this scheme, so that the base-level +// * traversals start close to the locations being found, inserted +// * or deleted -- usually base level traversals only traverse a few +// * nodes. This doesn't change the basic algorithm except for the +// * need to make sure base traversals start at predecessors (here, +// * b) that are not (structurally) deleted, otherwise retrying +// * after processing the deletion. +// * +// * Index levels are maintained as lists with volatile next fields, +// * using CAS to link and unlink. Races are allowed in index-list +// * operations that can (rarely) fail to link in a new index node +// * or delete one. (We can't do this of course for data nodes.) +// * However, even when this happens, the index lists remain sorted, +// * so correctly serve as indices. This can impact performance, +// * but since skip lists are probabilistic anyway, the net result +// * is that under contention, the effective "p" value may be lower +// * than its nominal value. And race windows are kept small enough +// * that in practice these failures are rare, even under a lot of +// * contention. +// * +// * The fact that retries (for both base and index lists) are +// * relatively cheap due to indexing allows some minor +// * simplifications of retry logic. Traversal restarts are +// * performed after most "helping-out" CASes. This isn't always +// * strictly necessary, but the implicit backoffs tend to help +// * reduce other downstream failed CAS's enough to outweigh restart +// * cost. This worsens the worst case, but seems to improve even +// * highly contended cases. +// * +// * Unlike most skip-list implementations, index insertion and +// * deletion here require a separate traversal pass occuring after +// * the base-level action, to add or remove index nodes. This adds +// * to single-threaded overhead, but improves contended +// * multithreaded performance by narrowing interference windows, +// * and allows deletion to ensure that all index nodes will be made +// * unreachable upon return from a public remove operation, thus +// * avoiding unwanted garbage retention. This is more important +// * here than in some other data structures because we cannot null +// * out node fields referencing user keys since they might still be +// * read by other ongoing traversals. +// * +// * Indexing uses skip list parameters that maintain good search +// * performance while using sparser-than-usual indices: The +// * hardwired parameters k=1, p=0.5 (see method randomLevel) mean +// * that about one-quarter of the nodes have indices. Of those that +// * do, half have one level, a quarter have two, and so on (see +// * Pugh's Skip List Cookbook, sec 3.4). The expected total space +// * requirement for a map is slightly less than for the current +// * implementation of java.util.TreeMap. +// * +// * Changing the level of the index (i.e, the height of the +// * tree-like structure) also uses CAS. The head index has initial +// * level/height of one. Creation of an index with height greater +// * than the current level adds a level to the head index by +// * CAS'ing on a new top-most head. To maintain good performance +// * after a lot of removals, deletion methods heuristically try to +// * reduce the height if the topmost levels appear to be empty. +// * This may encounter races in which it possible (but rare) to +// * reduce and "lose" a level just as it is about to contain an +// * index (that will then never be encountered). This does no +// * structural harm, and in practice appears to be a better option +// * than allowing unrestrained growth of levels. +// * +// * The code for all this is more verbose than you'd like. Most +// * operations entail locating an element (or position to insert an +// * element). The code to do this can't be nicely factored out +// * because subsequent uses require a snapshot of predecessor +// * and/or successor and/or value fields which can't be returned +// * all at once, at least not without creating yet another object +// * to hold them -- creating such little objects is an especially +// * bad idea for basic internal search operations because it adds +// * to GC overhead. (This is one of the few times I've wished Java +// * had macros.) Instead, some traversal code is interleaved within +// * insertion and removal operations. The control logic to handle +// * all the retry conditions is sometimes twisty. Most search is +// * broken into 2 parts. findPredecessor() searches index nodes +// * only, returning a base-level predecessor of the key. findNode() +// * finishes out the base-level search. Even with this factoring, +// * there is a fair amount of near-duplication of code to handle +// * variants. +// * +// * For explanation of algorithms sharing at least a couple of +// * features with this one, see Mikhail Fomitchev's thesis +// * (http://www.cs.yorku.ca/~mikhail/), Keir Fraser's thesis +// * (http://www.cl.cam.ac.uk/users/kaf24/), and Hakan Sundell's +// * thesis (http://www.cs.chalmers.se/~phs/). +// * +// * Given the use of tree-like index nodes, you might wonder why +// * this doesn't use some kind of search tree instead, which would +// * support somewhat faster search operations. The reason is that +// * there are no known efficient lock-free insertion and deletion +// * algorithms for search trees. The immutability of the "down" +// * links of index nodes (as opposed to mutable "left" fields in +// * true trees) makes this tractable using only CAS operations. +// * +// * Notation guide for local variables +// * Node: b, n, f for predecessor, node, successor +// * Index: q, r, d for index node, right, down. +// * t for another index node +// * Head: h +// * Levels: j +// * Keys: k, key +// * Values: v, value +// * Comparisons: c +// */ +// +// private static final long serialVersionUID = -8627078645895051609L; +// +// /** +// * Generates the initial random seed for the cheaper per-instance +// * random number generators used in randomLevel. +// */ +// private static final Random seedGenerator = new Random(); +// +// /** +// * Special value used to identify base-level header +// */ +// private static final Object BASE_HEADER = new Object(); +// +// /** +// * The topmost head index of the skiplist. +// */ +// private transient volatile HeadIndex head; +// +// /** +// * The comparator used to maintain order in this map, or null +// * if using natural ordering. +// * @serial +// */ +// private final Comparator comparator; +// +// /** +// * Seed for simple random number generator. Not volatile since it +// * doesn't matter too much if different threads don't see updates. +// */ +// private transient int randomSeed; +// +// /** Lazily initialized key set */ +// private transient KeySet keySet; +// /** Lazily initialized entry set */ +// private transient EntrySet entrySet; +// /** Lazily initialized values collection */ +// private transient Values values; +// /** Lazily initialized descending key set */ +// private transient ConcurrentNavigableMap descendingMap; +// +// /** +// * Initializes or resets state. Needed by constructors, clone, +// * clear, readObject. and ConcurrentSkipListSet.clone. +// * (Note that comparator must be separately initialized.) +// */ +// final void initialize() { +// keySet = null; +// entrySet = null; +// values = null; +// descendingMap = null; +// randomSeed = seedGenerator.nextInt() | 0x0100; // ensure nonzero +// head = new HeadIndex(new Node(null, BASE_HEADER, null), +// null, null, 1); +// } +// +// /** Updater for casHead */ +// private static final +// AtomicReferenceFieldUpdater +// headUpdater = AtomicReferenceFieldUpdater.newUpdater +// (ConcurrentSkipListMap.class, HeadIndex.class, "head"); +// +// /** +// * compareAndSet head node +// */ +// private boolean casHead(HeadIndex cmp, HeadIndex val) { +// return headUpdater.compareAndSet(this, cmp, val); +// } +// +// /* ---------------- Nodes -------------- */ +// +// /** +// * Nodes hold keys and values, and are singly linked in sorted +// * order, possibly with some intervening marker nodes. The list is +// * headed by a dummy node accessible as head.node. The value field +// * is declared only as Object because it takes special non-V +// * values for marker and header nodes. +// */ +// static final class Node { +// final K key; +// volatile Object value; +// volatile Node next_; +// +// /** +// * Creates a new regular node. +// */ +// Node(K key, Object value, Node next) { +// this.key = key; +// this.value = value; +// this.next_ = next; +// } +// +// /** +// * Creates a new marker node. A marker is distinguished by +// * having its value field point to itself. Marker nodes also +// * have null keys, a fact that is exploited in a few places, +// * but this doesn't distinguish markers from the base-level +// * header node (head.node), which also has a null key. +// */ +// Node(Node next) { +// this.key = null; +// this.value = this; +// this.next_ = next; +// } +// +// /** Updater for casNext */ +// static final AtomicReferenceFieldUpdater +// nextUpdater = AtomicReferenceFieldUpdater.newUpdater +// (Node.class, Node.class, "next"); +// +// /** Updater for casValue */ +// static final AtomicReferenceFieldUpdater +// valueUpdater = AtomicReferenceFieldUpdater.newUpdater +// (Node.class, Object.class, "value"); +// +// /** +// * compareAndSet value field +// */ +// boolean casValue(Object cmp, Object val) { +// return valueUpdater.compareAndSet(this, cmp, val); +// } +// +// /** +// * compareAndSet next field +// */ +// boolean casNext(Node cmp, Node val) { +// return nextUpdater.compareAndSet(this, cmp, val); +// } +// +// /** +// * Returns true if this node is a marker. This method isn't +// * actually called in any current code checking for markers +// * because callers will have already read value field and need +// * to use that read (not another done here) and so directly +// * test if value points to node. +// * @param n a possibly null reference to a node +// * @return true if this node is a marker node +// */ +// boolean isMarker() { +// return value == this; +// } +// +// /** +// * Returns true if this node is the header of base-level list. +// * @return true if this node is header node +// */ +// boolean isBaseHeader() { +// return value == BASE_HEADER; +// } +// +// /** +// * Tries to append a deletion marker to this node. +// * @param f the assumed current successor of this node +// * @return true if successful +// */ +// boolean appendMarker(Node f) { +// return casNext(f, new Node(f)); +// } +// +// /** +// * Helps out a deletion by appending marker or unlinking from +// * predecessor. This is called during traversals when value +// * field seen to be null. +// * @param b predecessor +// * @param f successor +// */ +// void helpDelete(Node b, Node f) { +// /* +// * Rechecking links and then doing only one of the +// * help-out stages per call tends to minimize CAS +// * interference among helping threads. +// */ +// if (f == next_ && this == b.next_) { +// if (f == null || f.value != f) // not already marked +// appendMarker(f); +// else +// b.casNext(this, f.next_); +// } +// } +// +// /** +// * Returns value if this node contains a valid key-value pair, +// * else null. +// * @return this node's value if it isn't a marker or header or +// * is deleted, else null. +// */ +// V getValidValue() { +// Object v = value; +// if (v == this || v == BASE_HEADER) +// return null; +// return (V)v; +// } +// +// /** +// * Creates and returns a new SimpleImmutableEntry holding current +// * mapping if this node holds a valid value, else null. +// * @return new entry or null +// */ +// AbstractMap.SimpleImmutableEntry createSnapshot() { +// V v = getValidValue(); +// if (v == null) +// return null; +// return new AbstractMap.SimpleImmutableEntry(key, v); +// } +// } +// +// /* ---------------- Indexing -------------- */ +// +// /** +// * Index nodes represent the levels of the skip list. Note that +// * even though both Nodes and Indexes have forward-pointing +// * fields, they have different types and are handled in different +// * ways, that can't nicely be captured by placing field in a +// * shared abstract class. +// */ +// static class Index { +// final Node node; +// final Index down; +// volatile Index right; +// +// /** +// * Creates index node with given values. +// */ +// Index(Node node, Index down, Index right) { +// this.node = node; +// this.down = down; +// this.right = right; +// } +// +// /** Updater for casRight */ +// static final AtomicReferenceFieldUpdater +// rightUpdater = AtomicReferenceFieldUpdater.newUpdater +// (Index.class, Index.class, "right"); +// +// /** +// * compareAndSet right field +// */ +// final boolean casRight(Index cmp, Index val) { +// return rightUpdater.compareAndSet(this, cmp, val); +// } +// +// /** +// * Returns true if the node this indexes has been deleted. +// * @return true if indexed node is known to be deleted +// */ +// final boolean indexesDeletedNode() { +// return node.value == null; +// } +// +// /** +// * Tries to CAS newSucc as successor. To minimize races with +// * unlink that may lose this index node, if the node being +// * indexed is known to be deleted, it doesn't try to link in. +// * @param succ the expected current successor +// * @param newSucc the new successor +// * @return true if successful +// */ +// final boolean link(Index succ, Index newSucc) { +// Node n = node; +// newSucc.right = succ; +// return n.value != null && casRight(succ, newSucc); +// } +// +// /** +// * Tries to CAS right field to skip over apparent successor +// * succ. Fails (forcing a retraversal by caller) if this node +// * is known to be deleted. +// * @param succ the expected current successor +// * @return true if successful +// */ +// final boolean unlink(Index succ) { +// return !indexesDeletedNode() && casRight(succ, succ.right); +// } +// } +// +// /* ---------------- Head nodes -------------- */ +// +// /** +// * Nodes heading each level keep track of their level. +// */ +// static final class HeadIndex extends Index { +// final int level; +// HeadIndex(Node node, Index down, Index right, int level) { +// super(node, down, right); +// this.level = level; +// } +// } +// +// /* ---------------- Comparison utilities -------------- */ +// +// /** +// * Represents a key with a comparator as a Comparable. +// * +// * Because most sorted collections seem to use natural ordering on +// * Comparables (Strings, Integers, etc), most internal methods are +// * geared to use them. This is generally faster than checking +// * per-comparison whether to use comparator or comparable because +// * it doesn't require a (Comparable) cast for each comparison. +// * (Optimizers can only sometimes remove such redundant checks +// * themselves.) When Comparators are used, +// * ComparableUsingComparators are created so that they act in the +// * same way as natural orderings. This penalizes use of +// * Comparators vs Comparables, which seems like the right +// * tradeoff. +// */ +// static final class ComparableUsingComparator implements Comparable { +// final K actualKey; +// final Comparator cmp; +// ComparableUsingComparator(K key, Comparator cmp) { +// this.actualKey = key; +// this.cmp = cmp; +// } +// public int compareTo(K k2) { +// return cmp.compare(actualKey, k2); +// } +// } +// +// /** +// * If using comparator, return a ComparableUsingComparator, else +// * cast key as Comparable, which may cause ClassCastException, +// * which is propagated back to caller. +// */ +// private Comparable comparable(Object key) throws ClassCastException { +// if (key == null) +// throw new NullPointerException(); +// if (comparator != null) +// return new ComparableUsingComparator((K)key, comparator); +// else +// return (Comparable)key; +// } +// +// /** +// * Compares using comparator or natural ordering. Used when the +// * ComparableUsingComparator approach doesn't apply. +// */ +// int compare(K k1, K k2) throws ClassCastException { +// Comparator cmp = comparator; +// if (cmp != null) +// return cmp.compare(k1, k2); +// else +// return ((Comparable)k1).compareTo(k2); +// } +// +// /** +// * Returns true if given key greater than or equal to least and +// * strictly less than fence, bypassing either test if least or +// * fence are null. Needed mainly in submap operations. +// */ +// boolean inHalfOpenRange(K key, K least, K fence) { +// if (key == null) +// throw new NullPointerException(); +// return ((least == null || compare(key, least) >= 0) && +// (fence == null || compare(key, fence) < 0)); +// } +// +// /** +// * Returns true if given key greater than or equal to least and less +// * or equal to fence. Needed mainly in submap operations. +// */ +// boolean inOpenRange(K key, K least, K fence) { +// if (key == null) +// throw new NullPointerException(); +// return ((least == null || compare(key, least) >= 0) && +// (fence == null || compare(key, fence) <= 0)); +// } +// +// /* ---------------- Traversal -------------- */ +// +// /** +// * Returns a base-level node with key strictly less than given key, +// * or the base-level header if there is no such node. Also +// * unlinks indexes to deleted nodes found along the way. Callers +// * rely on this side-effect of clearing indices to deleted nodes. +// * @param key the key +// * @return a predecessor of key +// */ +// private Node findPredecessor(Comparable key) { +// if (key == null) +// throw new NullPointerException(); // don't postpone errors +// for (;;) { +// Index q = head; +// Index r = q.right; +// for (;;) { +// if (r != null) { +// Node n = r.node; +// K k = n.key; +// if (n.value == null) { +// if (!q.unlink(r)) +// break; // restart +// r = q.right; // reread r +// continue; +// } +// if (key.compareTo(k) > 0) { +// q = r; +// r = r.right; +// continue; +// } +// } +// Index d = q.down; +// if (d != null) { +// q = d; +// r = d.right; +// } else +// return q.node; +// } +// } +// } +// +// /** +// * Returns node holding key or null if no such, clearing out any +// * deleted nodes seen along the way. Repeatedly traverses at +// * base-level looking for key starting at predecessor returned +// * from findPredecessor, processing base-level deletions as +// * encountered. Some callers rely on this side-effect of clearing +// * deleted nodes. +// * +// * Restarts occur, at traversal step centered on node n, if: +// * +// * (1) After reading n's next field, n is no longer assumed +// * predecessor b's current successor, which means that +// * we don't have a consistent 3-node snapshot and so cannot +// * unlink any subsequent deleted nodes encountered. +// * +// * (2) n's value field is null, indicating n is deleted, in +// * which case we help out an ongoing structural deletion +// * before retrying. Even though there are cases where such +// * unlinking doesn't require restart, they aren't sorted out +// * here because doing so would not usually outweigh cost of +// * restarting. +// * +// * (3) n is a marker or n's predecessor's value field is null, +// * indicating (among other possibilities) that +// * findPredecessor returned a deleted node. We can't unlink +// * the node because we don't know its predecessor, so rely +// * on another call to findPredecessor to notice and return +// * some earlier predecessor, which it will do. This check is +// * only strictly needed at beginning of loop, (and the +// * b.value check isn't strictly needed at all) but is done +// * each iteration to help avoid contention with other +// * threads by callers that will fail to be able to change +// * links, and so will retry anyway. +// * +// * The traversal loops in doPut, doRemove, and findNear all +// * include the same three kinds of checks. And specialized +// * versions appear in findFirst, and findLast and their +// * variants. They can't easily share code because each uses the +// * reads of fields held in locals occurring in the orders they +// * were performed. +// * +// * @param key the key +// * @return node holding key, or null if no such +// */ +// private Node findNode(Comparable key) { +// for (;;) { +// Node b = findPredecessor(key); +// Node n = b.next_; +// for (;;) { +// if (n == null) +// return null; +// Node f = n.next_; +// if (n != b.next_) // inconsistent read +// break; +// Object v = n.value; +// if (v == null) { // n is deleted +// n.helpDelete(b, f); +// break; +// } +// if (v == n || b.value == null) // b is deleted +// break; +// int c = key.compareTo(n.key); +// if (c == 0) +// return n; +// if (c < 0) +// return null; +// b = n; +// n = f; +// } +// } +// } +// +// /** +// * Specialized variant of findNode to perform Map.get. Does a weak +// * traversal, not bothering to fix any deleted index nodes, +// * returning early if it happens to see key in index, and passing +// * over any deleted base nodes, falling back to getUsingFindNode +// * only if it would otherwise return value from an ongoing +// * deletion. Also uses "bound" to eliminate need for some +// * comparisons (see Pugh Cookbook). Also folds uses of null checks +// * and node-skipping because markers have null keys. +// * @param okey the key +// * @return the value, or null if absent +// */ +// private V doGet(Object okey) { +// Comparable key = comparable(okey); +// Node bound = null; +// Index q = head; +// Index r = q.right; +// Node n; +// K k; +// int c; +// for (;;) { +// Index d; +// // Traverse rights +// if (r != null && (n = r.node) != bound && (k = n.key) != null) { +// if ((c = key.compareTo(k)) > 0) { +// q = r; +// r = r.right; +// continue; +// } else if (c == 0) { +// Object v = n.value; +// return (v != null)? (V)v : getUsingFindNode(key); +// } else +// bound = n; +// } +// +// // Traverse down +// if ((d = q.down) != null) { +// q = d; +// r = d.right; +// } else +// break; +// } +// +// // Traverse nexts +// for (n = q.node.next_; n != null; n = n.next_) { +// if ((k = n.key) != null) { +// if ((c = key.compareTo(k)) == 0) { +// Object v = n.value; +// return (v != null)? (V)v : getUsingFindNode(key); +// } else if (c < 0) +// break; +// } +// } +// return null; +// } +// +// /** +// * Performs map.get via findNode. Used as a backup if doGet +// * encounters an in-progress deletion. +// * @param key the key +// * @return the value, or null if absent +// */ +// private V getUsingFindNode(Comparable key) { +// /* +// * Loop needed here and elsewhere in case value field goes +// * null just as it is about to be returned, in which case we +// * lost a race with a deletion, so must retry. +// */ +// for (;;) { +// Node n = findNode(key); +// if (n == null) +// return null; +// Object v = n.value; +// if (v != null) +// return (V)v; +// } +// } +// +// /* ---------------- Insertion -------------- */ +// +// /** +// * Main insertion method. Adds element if not present, or +// * replaces value if present and onlyIfAbsent is false. +// * @param kkey the key +// * @param value the value that must be associated with key +// * @param onlyIfAbsent if should not insert if already present +// * @return the old value, or null if newly inserted +// */ +// private V doPut(K kkey, V value, boolean onlyIfAbsent) { +// Comparable key = comparable(kkey); +// for (;;) { +// Node b = findPredecessor(key); +// Node n = b.next_; +// for (;;) { +// if (n != null) { +// Node f = n.next_; +// if (n != b.next_) // inconsistent read +// break;; +// Object v = n.value; +// if (v == null) { // n is deleted +// n.helpDelete(b, f); +// break; +// } +// if (v == n || b.value == null) // b is deleted +// break; +// int c = key.compareTo(n.key); +// if (c > 0) { +// b = n; +// n = f; +// continue; +// } +// if (c == 0) { +// if (onlyIfAbsent || n.casValue(v, value)) +// return (V)v; +// else +// break; // restart if lost race to replace value +// } +// // else c < 0; fall through +// } +// +// Node z = new Node(kkey, value, n); +// if (!b.casNext(n, z)) +// break; // restart if lost race to append to b +// int level = randomLevel(); +// if (level > 0) +// insertIndex(z, level); +// return null; +// } +// } +// } +// +// /** +// * Returns a random level for inserting a new node. +// * Hardwired to k=1, p=0.5, max 31 (see above and +// * Pugh's "Skip List Cookbook", sec 3.4). +// * +// * This uses the simplest of the generators described in George +// * Marsaglia's "Xorshift RNGs" paper. This is not a high-quality +// * generator but is acceptable here. +// */ +// private int randomLevel() { +// int x = randomSeed; +// x ^= x << 13; +// x ^= x >>> 17; +// randomSeed = x ^= x << 5; +// if ((x & 0x8001) != 0) // test highest and lowest bits +// return 0; +// int level = 1; +// while (((x >>>= 1) & 1) != 0) ++level; +// return level; +// } +// +// /** +// * Creates and adds index nodes for the given node. +// * @param z the node +// * @param level the level of the index +// */ +// private void insertIndex(Node z, int level) { +// HeadIndex h = head; +// int max = h.level; +// +// if (level <= max) { +// Index idx = null; +// for (int i = 1; i <= level; ++i) +// idx = new Index(z, idx, null); +// addIndex(idx, h, level); +// +// } else { // Add a new level +// /* +// * To reduce interference by other threads checking for +// * empty levels in tryReduceLevel, new levels are added +// * with initialized right pointers. Which in turn requires +// * keeping levels in an array to access them while +// * creating new head index nodes from the opposite +// * direction. +// */ +// level = max + 1; +// Index[] idxs = (Index[])new Index[level+1]; +// Index idx = null; +// for (int i = 1; i <= level; ++i) +// idxs[i] = idx = new Index(z, idx, null); +// +// HeadIndex oldh; +// int k; +// for (;;) { +// oldh = head; +// int oldLevel = oldh.level; +// if (level <= oldLevel) { // lost race to add level +// k = level; +// break; +// } +// HeadIndex newh = oldh; +// Node oldbase = oldh.node; +// for (int j = oldLevel+1; j <= level; ++j) +// newh = new HeadIndex(oldbase, newh, idxs[j], j); +// if (casHead(oldh, newh)) { +// k = oldLevel; +// break; +// } +// } +// addIndex(idxs[k], oldh, k); +// } +// } +// +// /** +// * Adds given index nodes from given level down to 1. +// * @param idx the topmost index node being inserted +// * @param h the value of head to use to insert. This must be +// * snapshotted by callers to provide correct insertion level +// * @param indexLevel the level of the index +// */ +// private void addIndex(Index idx, HeadIndex h, int indexLevel) { +// // Track next level to insert in case of retries +// int insertionLevel = indexLevel; +// Comparable key = comparable(idx.node.key); +// if (key == null) throw new NullPointerException(); +// +// // Similar to findPredecessor, but adding index nodes along +// // path to key. +// for (;;) { +// int j = h.level; +// Index q = h; +// Index r = q.right; +// Index t = idx; +// for (;;) { +// if (r != null) { +// Node n = r.node; +// // compare before deletion check avoids needing recheck +// int c = key.compareTo(n.key); +// if (n.value == null) { +// if (!q.unlink(r)) +// break; +// r = q.right; +// continue; +// } +// if (c > 0) { +// q = r; +// r = r.right; +// continue; +// } +// } +// +// if (j == insertionLevel) { +// // Don't insert index if node already deleted +// if (t.indexesDeletedNode()) { +// findNode(key); // cleans up +// return; +// } +// if (!q.link(r, t)) +// break; // restart +// if (--insertionLevel == 0) { +// // need final deletion check before return +// if (t.indexesDeletedNode()) +// findNode(key); +// return; +// } +// } +// +// if (--j >= insertionLevel && j < indexLevel) +// t = t.down; +// q = q.down; +// r = q.right; +// } +// } +// } +// +// /* ---------------- Deletion -------------- */ +// +// /** +// * Main deletion method. Locates node, nulls value, appends a +// * deletion marker, unlinks predecessor, removes associated index +// * nodes, and possibly reduces head index level. +// * +// * Index nodes are cleared out simply by calling findPredecessor. +// * which unlinks indexes to deleted nodes found along path to key, +// * which will include the indexes to this node. This is done +// * unconditionally. We can't check beforehand whether there are +// * index nodes because it might be the case that some or all +// * indexes hadn't been inserted yet for this node during initial +// * search for it, and we'd like to ensure lack of garbage +// * retention, so must call to be sure. +// * +// * @param okey the key +// * @param value if non-null, the value that must be +// * associated with key +// * @return the node, or null if not found +// */ +// final V doRemove(Object okey, Object value) { +// Comparable key = comparable(okey); +// for (;;) { +// Node b = findPredecessor(key); +// Node n = b.next_; +// for (;;) { +// if (n == null) +// return null; +// Node f = n.next_; +// if (n != b.next_) // inconsistent read +// break; +// Object v = n.value; +// if (v == null) { // n is deleted +// n.helpDelete(b, f); +// break; +// } +// if (v == n || b.value == null) // b is deleted +// break; +// int c = key.compareTo(n.key); +// if (c < 0) +// return null; +// if (c > 0) { +// b = n; +// n = f; +// continue; +// } +// if (value != null && !value.equals(v)) +// return null; +// if (!n.casValue(v, null)) +// break; +// if (!n.appendMarker(f) || !b.casNext(n, f)) +// findNode(key); // Retry via findNode +// else { +// findPredecessor(key); // Clean index +// if (head.right == null) +// tryReduceLevel(); +// } +// return (V)v; +// } +// } +// } +// +// /** +// * Possibly reduce head level if it has no nodes. This method can +// * (rarely) make mistakes, in which case levels can disappear even +// * though they are about to contain index nodes. This impacts +// * performance, not correctness. To minimize mistakes as well as +// * to reduce hysteresis, the level is reduced by one only if the +// * topmost three levels look empty. Also, if the removed level +// * looks non-empty after CAS, we try to change it back quick +// * before anyone notices our mistake! (This trick works pretty +// * well because this method will practically never make mistakes +// * unless current thread stalls immediately before first CAS, in +// * which case it is very unlikely to stall again immediately +// * afterwards, so will recover.) +// * +// * We put up with all this rather than just let levels grow +// * because otherwise, even a small map that has undergone a large +// * number of insertions and removals will have a lot of levels, +// * slowing down access more than would an occasional unwanted +// * reduction. +// */ +// private void tryReduceLevel() { +// HeadIndex h = head; +// HeadIndex d; +// HeadIndex e; +// if (h.level > 3 && +// (d = (HeadIndex)h.down) != null && +// (e = (HeadIndex)d.down) != null && +// e.right == null && +// d.right == null && +// h.right == null && +// casHead(h, d) && // try to set +// h.right != null) // recheck +// casHead(d, h); // try to backout +// } +// +// /* ---------------- Finding and removing first element -------------- */ +// +// /** +// * Specialized variant of findNode to get first valid node. +// * @return first node or null if empty +// */ +// Node findFirst() { +// for (;;) { +// Node b = head.node; +// Node n = b.next_; +// if (n == null) +// return null; +// if (n.value != null) +// return n; +// n.helpDelete(b, n.next_); +// } +// } +// +// /** +// * Removes first entry; returns its snapshot. +// * @return null if empty, else snapshot of first entry +// */ +// Map.Entry doRemoveFirstEntry() { +// for (;;) { +// Node b = head.node; +// Node n = b.next_; +// if (n == null) +// return null; +// Node f = n.next_; +// if (n != b.next_) +// continue; +// Object v = n.value; +// if (v == null) { +// n.helpDelete(b, f); +// continue; +// } +// if (!n.casValue(v, null)) +// continue; +// if (!n.appendMarker(f) || !b.casNext(n, f)) +// findFirst(); // retry +// clearIndexToFirst(); +// return new AbstractMap.SimpleImmutableEntry(n.key, (V)v); +// } +// } +// +// /** +// * Clears out index nodes associated with deleted first entry. +// */ +// private void clearIndexToFirst() { +// for (;;) { +// Index q = head; +// for (;;) { +// Index r = q.right; +// if (r != null && r.indexesDeletedNode() && !q.unlink(r)) +// break; +// if ((q = q.down) == null) { +// if (head.right == null) +// tryReduceLevel(); +// return; +// } +// } +// } +// } +// +// +// /* ---------------- Finding and removing last element -------------- */ +// +// /** +// * Specialized version of find to get last valid node. +// * @return last node or null if empty +// */ +// Node findLast() { +// /* +// * findPredecessor can't be used to traverse index level +// * because this doesn't use comparisons. So traversals of +// * both levels are folded together. +// */ +// Index q = head; +// for (;;) { +// Index d, r; +// if ((r = q.right) != null) { +// if (r.indexesDeletedNode()) { +// q.unlink(r); +// q = head; // restart +// } +// else +// q = r; +// } else if ((d = q.down) != null) { +// q = d; +// } else { +// Node b = q.node; +// Node n = b.next_; +// for (;;) { +// if (n == null) +// return (b.isBaseHeader())? null : b; +// Node f = n.next_; // inconsistent read +// if (n != b.next_) +// break; +// Object v = n.value; +// if (v == null) { // n is deleted +// n.helpDelete(b, f); +// break; +// } +// if (v == n || b.value == null) // b is deleted +// break; +// b = n; +// n = f; +// } +// q = head; // restart +// } +// } +// } +// +// /** +// * Specialized variant of findPredecessor to get predecessor of last +// * valid node. Needed when removing the last entry. It is possible +// * that all successors of returned node will have been deleted upon +// * return, in which case this method can be retried. +// * @return likely predecessor of last node +// */ +// private Node findPredecessorOfLast() { +// for (;;) { +// Index q = head; +// for (;;) { +// Index d, r; +// if ((r = q.right) != null) { +// if (r.indexesDeletedNode()) { +// q.unlink(r); +// break; // must restart +// } +// // proceed as far across as possible without overshooting +// if (r.node.next_ != null) { +// q = r; +// continue; +// } +// } +// if ((d = q.down) != null) +// q = d; +// else +// return q.node; +// } +// } +// } +// +// /** +// * Removes last entry; returns its snapshot. +// * Specialized variant of doRemove. +// * @return null if empty, else snapshot of last entry +// */ +// Map.Entry doRemoveLastEntry() { +// for (;;) { +// Node b = findPredecessorOfLast(); +// Node n = b.next_; +// if (n == null) { +// if (b.isBaseHeader()) // empty +// return null; +// else +// continue; // all b's successors are deleted; retry +// } +// for (;;) { +// Node f = n.next_; +// if (n != b.next_) // inconsistent read +// break; +// Object v = n.value; +// if (v == null) { // n is deleted +// n.helpDelete(b, f); +// break; +// } +// if (v == n || b.value == null) // b is deleted +// break; +// if (f != null) { +// b = n; +// n = f; +// continue; +// } +// if (!n.casValue(v, null)) +// break; +// K key = n.key; +// Comparable ck = comparable(key); +// if (!n.appendMarker(f) || !b.casNext(n, f)) +// findNode(ck); // Retry via findNode +// else { +// findPredecessor(ck); // Clean index +// if (head.right == null) +// tryReduceLevel(); +// } +// return new AbstractMap.SimpleImmutableEntry(key, (V)v); +// } +// } +// } +// +// /* ---------------- Relational operations -------------- */ +// +// // Control values OR'ed as arguments to findNear +// +// private static final int EQ = 1; +// private static final int LT = 2; +// private static final int GT = 0; // Actually checked as !LT +// +// /** +// * Utility for ceiling, floor, lower, higher methods. +// * @param kkey the key +// * @param rel the relation -- OR'ed combination of EQ, LT, GT +// * @return nearest node fitting relation, or null if no such +// */ +// Node findNear(K kkey, int rel) { +// Comparable key = comparable(kkey); +// for (;;) { +// Node b = findPredecessor(key); +// Node n = b.next_; +// for (;;) { +// if (n == null) +// return ((rel & LT) == 0 || b.isBaseHeader())? null : b; +// Node f = n.next_; +// if (n != b.next_) // inconsistent read +// break; +// Object v = n.value; +// if (v == null) { // n is deleted +// n.helpDelete(b, f); +// break; +// } +// if (v == n || b.value == null) // b is deleted +// break; +// int c = key.compareTo(n.key); +// if ((c == 0 && (rel & EQ) != 0) || +// (c < 0 && (rel & LT) == 0)) +// return n; +// if ( c <= 0 && (rel & LT) != 0) +// return (b.isBaseHeader())? null : b; +// b = n; +// n = f; +// } +// } +// } +// +// /** +// * Returns SimpleImmutableEntry for results of findNear. +// * @param key the key +// * @param rel the relation -- OR'ed combination of EQ, LT, GT +// * @return Entry fitting relation, or null if no such +// */ +// AbstractMap.SimpleImmutableEntry getNear(K key, int rel) { +// for (;;) { +// Node n = findNear(key, rel); +// if (n == null) +// return null; +// AbstractMap.SimpleImmutableEntry e = n.createSnapshot(); +// if (e != null) +// return e; +// } +// } +// +// +// /* ---------------- Constructors -------------- */ +// /** * Constructs a new, empty map, sorted according to the * {@linkplain Comparable natural ordering} of the keys. */ public ConcurrentSkipListMap() { - this.comparator = null; - initialize(); +// this.comparator = null; +// initialize(); } - +// /** * Constructs a new, empty map, sorted according to the specified * comparator. @@ -1435,10 +1440,10 @@ public ConcurrentSkipListMap() { * ordering} of the keys will be used. */ public ConcurrentSkipListMap(Comparator comparator) { - this.comparator = comparator; - initialize(); +// this.comparator = comparator; +// initialize(); } - +// /** * Constructs a new map containing the same mappings as the given map, * sorted according to the {@linkplain Comparable natural ordering} of @@ -1451,9 +1456,10 @@ public ConcurrentSkipListMap(Comparator comparator) { * or values are null */ public ConcurrentSkipListMap(Map m) { - this.comparator = null; - initialize(); - putAll(m); + super(m); +// this.comparator = null; +// initialize(); +// putAll(m); } /** @@ -1466,1679 +1472,1686 @@ public ConcurrentSkipListMap(Map m) { * its keys or values are null */ public ConcurrentSkipListMap(SortedMap m) { - this.comparator = m.comparator(); - initialize(); - buildFromSorted(m); - } - - /** - * Returns a shallow copy of this ConcurrentSkipListMap - * instance. (The keys and values themselves are not cloned.) - * - * @return a shallow copy of this map - */ - public ConcurrentSkipListMap clone() { - ConcurrentSkipListMap clone = null; - try { - clone = (ConcurrentSkipListMap) super.clone(); - } catch (CloneNotSupportedException e) { - throw new InternalError(); - } - - clone.initialize(); - clone.buildFromSorted(this); - return clone; - } - - /** - * Streamlined bulk insertion to initialize from elements of - * given sorted map. Call only from constructor or clone - * method. - */ - private void buildFromSorted(SortedMap map) { - if (map == null) - throw new NullPointerException(); - - HeadIndex h = head; - Node basepred = h.node; - - // Track the current rightmost node at each level. Uses an - // ArrayList to avoid committing to initial or maximum level. - ArrayList> preds = new ArrayList>(); - - // initialize - for (int i = 0; i <= h.level; ++i) - preds.add(null); - Index q = h; - for (int i = h.level; i > 0; --i) { - preds.set(i, q); - q = q.down; - } - - Iterator> it = - map.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry e = it.next(); - int j = randomLevel(); - if (j > h.level) j = h.level + 1; - K k = e.getKey(); - V v = e.getValue(); - if (k == null || v == null) - throw new NullPointerException(); - Node z = new Node(k, v, null); - basepred.next_ = z; - basepred = z; - if (j > 0) { - Index idx = null; - for (int i = 1; i <= j; ++i) { - idx = new Index(z, idx, null); - if (i > h.level) - h = new HeadIndex(h.node, h, idx, i); - - if (i < preds.size()) { - preds.get(i).right = idx; - preds.set(i, idx); - } else - preds.add(idx); - } - } - } - head = h; - } - - /* ---------------- Serialization -------------- */ - - /** - * Save the state of this map to a stream. - * - * @serialData The key (Object) and value (Object) for each - * key-value mapping represented by the map, followed by - * null. The key-value mappings are emitted in key-order - * (as determined by the Comparator, or by the keys' natural - * ordering if no Comparator). - */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - // Write out the Comparator and any hidden stuff - s.defaultWriteObject(); - - // Write out keys and values (alternating) - for (Node n = findFirst(); n != null; n = n.next_) { - V v = n.getValidValue(); - if (v != null) { - s.writeObject(n.key); - s.writeObject(v); - } - } - s.writeObject(null); - } - - /** - * Reconstitute the map from a stream. - */ - private void readObject(final java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - // Read in the Comparator and any hidden stuff - s.defaultReadObject(); - // Reset transients - initialize(); - - /* - * This is nearly identical to buildFromSorted, but is - * distinct because readObject calls can't be nicely adapted - * as the kind of iterator needed by buildFromSorted. (They - * can be, but doing so requires type cheats and/or creation - * of adaptor classes.) It is simpler to just adapt the code. - */ - - HeadIndex h = head; - Node basepred = h.node; - ArrayList> preds = new ArrayList>(); - for (int i = 0; i <= h.level; ++i) - preds.add(null); - Index q = h; - for (int i = h.level; i > 0; --i) { - preds.set(i, q); - q = q.down; - } - - for (;;) { - Object k = s.readObject(); - if (k == null) - break; - Object v = s.readObject(); - if (v == null) - throw new NullPointerException(); - K key = (K) k; - V val = (V) v; - int j = randomLevel(); - if (j > h.level) j = h.level + 1; - Node z = new Node(key, val, null); - basepred.next_ = z; - basepred = z; - if (j > 0) { - Index idx = null; - for (int i = 1; i <= j; ++i) { - idx = new Index(z, idx, null); - if (i > h.level) - h = new HeadIndex(h.node, h, idx, i); - - if (i < preds.size()) { - preds.get(i).right = idx; - preds.set(i, idx); - } else - preds.add(idx); - } - } - } - head = h; - } - - /* ------ Map API methods ------ */ - - /** - * Returns true if this map contains a mapping for the specified - * key. - * - * @param key key whose presence in this map is to be tested - * @return true if this map contains a mapping for the specified key - * @throws ClassCastException if the specified key cannot be compared - * with the keys currently in the map - * @throws NullPointerException if the specified key is null - */ - public boolean containsKey(Object key) { - return doGet(key) != null; - } - - /** - * Returns the value to which the specified key is mapped, - * or {@code null} if this map contains no mapping for the key. - * - *

More formally, if this map contains a mapping from a key - * {@code k} to a value {@code v} such that {@code key} compares - * equal to {@code k} according to the map's ordering, then this - * method returns {@code v}; otherwise it returns {@code null}. - * (There can be at most one such mapping.) - * - * @throws ClassCastException if the specified key cannot be compared - * with the keys currently in the map - * @throws NullPointerException if the specified key is null - */ - public V get(Object key) { - return doGet(key); - } - - /** - * Associates the specified value with the specified key in this map. - * If the map previously contained a mapping for the key, the old - * value is replaced. - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * @return the previous value associated with the specified key, or - * null if there was no mapping for the key - * @throws ClassCastException if the specified key cannot be compared - * with the keys currently in the map - * @throws NullPointerException if the specified key or value is null - */ - public V put(K key, V value) { - if (value == null) - throw new NullPointerException(); - return doPut(key, value, false); - } - - /** - * Removes the mapping for the specified key from this map if present. - * - * @param key key for which mapping should be removed - * @return the previous value associated with the specified key, or - * null if there was no mapping for the key - * @throws ClassCastException if the specified key cannot be compared - * with the keys currently in the map - * @throws NullPointerException if the specified key is null - */ - public V remove(Object key) { - return doRemove(key, null); - } - - /** - * Returns true if this map maps one or more keys to the - * specified value. This operation requires time linear in the - * map size. - * - * @param value value whose presence in this map is to be tested - * @return true if a mapping to value exists; - * false otherwise - * @throws NullPointerException if the specified value is null - */ - public boolean containsValue(Object value) { - if (value == null) - throw new NullPointerException(); - for (Node n = findFirst(); n != null; n = n.next_) { - V v = n.getValidValue(); - if (v != null && value.equals(v)) - return true; - } - return false; - } - - /** - * Returns the number of key-value mappings in this map. If this map - * contains more than Integer.MAX_VALUE elements, it - * returns Integer.MAX_VALUE. - * - *

Beware that, unlike in most collections, this method is - * NOT a constant-time operation. Because of the - * asynchronous nature of these maps, determining the current - * number of elements requires traversing them all to count them. - * Additionally, it is possible for the size to change during - * execution of this method, in which case the returned result - * will be inaccurate. Thus, this method is typically not very - * useful in concurrent applications. - * - * @return the number of elements in this map - */ - public int size() { - long count = 0; - for (Node n = findFirst(); n != null; n = n.next_) { - if (n.getValidValue() != null) - ++count; - } - return (count >= Integer.MAX_VALUE)? Integer.MAX_VALUE : (int)count; - } - - /** - * Returns true if this map contains no key-value mappings. - * @return true if this map contains no key-value mappings - */ - public boolean isEmpty() { - return findFirst() == null; - } - - /** - * Removes all of the mappings from this map. - */ - public void clear() { - initialize(); - } - - /* ---------------- View methods -------------- */ - - /* - * Note: Lazy initialization works for views because view classes - * are stateless/immutable so it doesn't matter wrt correctness if - * more than one is created (which will only rarely happen). Even - * so, the following idiom conservatively ensures that the method - * returns the one it created if it does so, not one created by - * another racing thread. - */ - - /** - * Returns a {@link NavigableSet} view of the keys contained in this map. - * The set's iterator returns the keys in ascending order. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. The set supports element - * removal, which removes the corresponding mapping from the map, - * via the {@code Iterator.remove}, {@code Set.remove}, - * {@code removeAll}, {@code retainAll}, and {@code clear} - * operations. It does not support the {@code add} or {@code addAll} - * operations. - * - *

The view's {@code iterator} is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. - * - *

This method is equivalent to method {@code navigableKeySet}. - * - * @return a navigable set view of the keys in this map - */ - public NavigableSet keySet() { - KeySet ks = keySet; - return (ks != null) ? ks : (keySet = new KeySet(this)); - } - - public NavigableSet navigableKeySet() { - KeySet ks = keySet; - return (ks != null) ? ks : (keySet = new KeySet(this)); - } - - /** - * Returns a {@link Collection} view of the values contained in this map. - * The collection's iterator returns the values in ascending order - * of the corresponding keys. - * The collection is backed by the map, so changes to the map are - * reflected in the collection, and vice-versa. The collection - * supports element removal, which removes the corresponding - * mapping from the map, via the Iterator.remove, - * Collection.remove, removeAll, - * retainAll and clear operations. It does not - * support the add or addAll operations. - * - *

The view's iterator is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. - */ - public Collection values() { - Values vs = values; - return (vs != null) ? vs : (values = new Values(this)); - } - - /** - * Returns a {@link Set} view of the mappings contained in this map. - * The set's iterator returns the entries in ascending key order. - * The set is backed by the map, so changes to the map are - * reflected in the set, and vice-versa. The set supports element - * removal, which removes the corresponding mapping from the map, - * via the Iterator.remove, Set.remove, - * removeAll, retainAll and clear - * operations. It does not support the add or - * addAll operations. - * - *

The view's iterator is a "weakly consistent" iterator - * that will never throw {@link ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. - * - *

The Map.Entry elements returned by - * iterator.next() do not support the - * setValue operation. - * - * @return a set view of the mappings contained in this map, - * sorted in ascending key order - */ - public Set> entrySet() { - EntrySet es = entrySet; - return (es != null) ? es : (entrySet = new EntrySet(this)); - } - - public ConcurrentNavigableMap descendingMap() { - ConcurrentNavigableMap dm = descendingMap; - return (dm != null) ? dm : (descendingMap = new SubMap - (this, null, false, null, false, true)); - } - - public NavigableSet descendingKeySet() { - return descendingMap().navigableKeySet(); - } - - /* ---------------- AbstractMap Overrides -------------- */ - - /** - * Compares the specified object with this map for equality. - * Returns true if the given object is also a map and the - * two maps represent the same mappings. More formally, two maps - * m1 and m2 represent the same mappings if - * m1.entrySet().equals(m2.entrySet()). This - * operation may return misleading results if either map is - * concurrently modified during execution of this method. - * - * @param o object to be compared for equality with this map - * @return true if the specified object is equal to this map - */ - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof Map)) - return false; - Map m = (Map) o; - try { - for (Map.Entry e : this.entrySet()) - if (! e.getValue().equals(m.get(e.getKey()))) - return false; - for (Map.Entry e : m.entrySet()) { - Object k = e.getKey(); - Object v = e.getValue(); - if (k == null || v == null || !v.equals(get(k))) - return false; - } - return true; - } catch (ClassCastException unused) { - return false; - } catch (NullPointerException unused) { - return false; - } - } - - /* ------ ConcurrentMap API methods ------ */ - - /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, - * or null if there was no mapping for the key - * @throws ClassCastException if the specified key cannot be compared - * with the keys currently in the map - * @throws NullPointerException if the specified key or value is null - */ - public V putIfAbsent(K key, V value) { - if (value == null) - throw new NullPointerException(); - return doPut(key, value, true); - } - - /** - * {@inheritDoc} - * - * @throws ClassCastException if the specified key cannot be compared - * with the keys currently in the map - * @throws NullPointerException if the specified key is null - */ - public boolean remove(Object key, Object value) { - if (key == null) - throw new NullPointerException(); - if (value == null) - return false; - return doRemove(key, value) != null; - } - - /** - * {@inheritDoc} - * - * @throws ClassCastException if the specified key cannot be compared - * with the keys currently in the map - * @throws NullPointerException if any of the arguments are null - */ - public boolean replace(K key, V oldValue, V newValue) { - if (oldValue == null || newValue == null) - throw new NullPointerException(); - Comparable k = comparable(key); - for (;;) { - Node n = findNode(k); - if (n == null) - return false; - Object v = n.value; - if (v != null) { - if (!oldValue.equals(v)) - return false; - if (n.casValue(v, newValue)) - return true; - } - } - } - - /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, - * or null if there was no mapping for the key - * @throws ClassCastException if the specified key cannot be compared - * with the keys currently in the map - * @throws NullPointerException if the specified key or value is null - */ - public V replace(K key, V value) { - if (value == null) - throw new NullPointerException(); - Comparable k = comparable(key); - for (;;) { - Node n = findNode(k); - if (n == null) - return null; - Object v = n.value; - if (v != null && n.casValue(v, value)) - return (V)v; - } - } - - /* ------ SortedMap API methods ------ */ - - public Comparator comparator() { - return comparator; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public K firstKey() { - Node n = findFirst(); - if (n == null) - throw new NoSuchElementException(); - return n.key; - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public K lastKey() { - Node n = findLast(); - if (n == null) - throw new NoSuchElementException(); - return n.key; - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code fromKey} or {@code toKey} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public ConcurrentNavigableMap subMap(K fromKey, - boolean fromInclusive, - K toKey, - boolean toInclusive) { - if (fromKey == null || toKey == null) - throw new NullPointerException(); - return new SubMap - (this, fromKey, fromInclusive, toKey, toInclusive, false); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code toKey} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public ConcurrentNavigableMap headMap(K toKey, - boolean inclusive) { - if (toKey == null) - throw new NullPointerException(); - return new SubMap - (this, null, false, toKey, inclusive, false); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code fromKey} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public ConcurrentNavigableMap tailMap(K fromKey, - boolean inclusive) { - if (fromKey == null) - throw new NullPointerException(); - return new SubMap - (this, fromKey, inclusive, null, false, false); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code fromKey} or {@code toKey} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public ConcurrentNavigableMap subMap(K fromKey, K toKey) { - return subMap(fromKey, true, toKey, false); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code toKey} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public ConcurrentNavigableMap headMap(K toKey) { - return headMap(toKey, false); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code fromKey} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public ConcurrentNavigableMap tailMap(K fromKey) { - return tailMap(fromKey, true); - } - - /* ---------------- Relational operations -------------- */ - - /** - * Returns a key-value mapping associated with the greatest key - * strictly less than the given key, or null if there is - * no such key. The returned entry does not support the - * Entry.setValue method. - * - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public Map.Entry lowerEntry(K key) { - return getNear(key, LT); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public K lowerKey(K key) { - Node n = findNear(key, LT); - return (n == null)? null : n.key; - } - - /** - * Returns a key-value mapping associated with the greatest key - * less than or equal to the given key, or null if there - * is no such key. The returned entry does not support - * the Entry.setValue method. - * - * @param key the key - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public Map.Entry floorEntry(K key) { - return getNear(key, LT|EQ); - } - - /** - * @param key the key - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public K floorKey(K key) { - Node n = findNear(key, LT|EQ); - return (n == null)? null : n.key; - } - - /** - * Returns a key-value mapping associated with the least key - * greater than or equal to the given key, or null if - * there is no such entry. The returned entry does not - * support the Entry.setValue method. - * - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public Map.Entry ceilingEntry(K key) { - return getNear(key, GT|EQ); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public K ceilingKey(K key) { - Node n = findNear(key, GT|EQ); - return (n == null)? null : n.key; - } - - /** - * Returns a key-value mapping associated with the least key - * strictly greater than the given key, or null if there - * is no such key. The returned entry does not support - * the Entry.setValue method. - * - * @param key the key - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public Map.Entry higherEntry(K key) { - return getNear(key, GT); - } - - /** - * @param key the key - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified key is null - */ - public K higherKey(K key) { - Node n = findNear(key, GT); - return (n == null)? null : n.key; - } - - /** - * Returns a key-value mapping associated with the least - * key in this map, or null if the map is empty. - * The returned entry does not support - * the Entry.setValue method. - */ - public Map.Entry firstEntry() { - for (;;) { - Node n = findFirst(); - if (n == null) - return null; - AbstractMap.SimpleImmutableEntry e = n.createSnapshot(); - if (e != null) - return e; - } - } - - /** - * Returns a key-value mapping associated with the greatest - * key in this map, or null if the map is empty. - * The returned entry does not support - * the Entry.setValue method. - */ - public Map.Entry lastEntry() { - for (;;) { - Node n = findLast(); - if (n == null) - return null; - AbstractMap.SimpleImmutableEntry e = n.createSnapshot(); - if (e != null) - return e; - } - } - - /** - * Removes and returns a key-value mapping associated with - * the least key in this map, or null if the map is empty. - * The returned entry does not support - * the Entry.setValue method. - */ - public Map.Entry pollFirstEntry() { - return doRemoveFirstEntry(); - } - - /** - * Removes and returns a key-value mapping associated with - * the greatest key in this map, or null if the map is empty. - * The returned entry does not support - * the Entry.setValue method. - */ - public Map.Entry pollLastEntry() { - return doRemoveLastEntry(); - } - - - /* ---------------- Iterators -------------- */ - - /** - * Base of iterator classes: - */ - abstract class Iter implements Iterator { - /** the last node returned by next() */ - Node lastReturned; - /** the next node to return from next(); */ - Node next; - /** Cache of next value field to maintain weak consistency */ - V nextValue; - - /** Initializes ascending iterator for entire range. */ - Iter() { - for (;;) { - next = findFirst(); - if (next == null) - break; - Object x = next.value; - if (x != null && x != next) { - nextValue = (V) x; - break; - } - } - } - - public final boolean hasNext() { - return next != null; - } - - /** Advances next to higher entry. */ - final void advance() { - if (next == null) - throw new NoSuchElementException(); - lastReturned = next; - for (;;) { - next = next.next_; - if (next == null) - break; - Object x = next.value; - if (x != null && x != next) { - nextValue = (V) x; - break; - } - } - } - - public void remove() { - Node l = lastReturned; - if (l == null) - throw new IllegalStateException(); - // It would not be worth all of the overhead to directly - // unlink from here. Using remove is fast enough. - ConcurrentSkipListMap.this.remove(l.key); - lastReturned = null; - } - - } - - final class ValueIterator extends Iter { - public V next() { - V v = nextValue; - advance(); - return v; - } - } - - final class KeyIterator extends Iter { - public K next() { - Node n = next; - advance(); - return n.key; - } - } - - final class EntryIterator extends Iter> { - public Map.Entry next() { - Node n = next; - V v = nextValue; - advance(); - return new AbstractMap.SimpleImmutableEntry(n.key, v); - } - } - - // Factory methods for iterators needed by ConcurrentSkipListSet etc - - Iterator keyIterator() { - return new KeyIterator(); - } - - Iterator valueIterator() { - return new ValueIterator(); - } - - Iterator> entryIterator() { - return new EntryIterator(); - } - - /* ---------------- View Classes -------------- */ - - /* - * View classes are static, delegating to a ConcurrentNavigableMap - * to allow use by SubMaps, which outweighs the ugliness of - * needing type-tests for Iterator methods. - */ - - static final List toList(Collection c) { - // Using size() here would be a pessimization. - List list = new ArrayList(); - for (E e : c) - list.add(e); - return list; - } - - static final class KeySet extends AbstractSet implements NavigableSet { - private final ConcurrentNavigableMap m; - KeySet(ConcurrentNavigableMap map) { m = map; } - public int size() { return m.size(); } - public boolean isEmpty() { return m.isEmpty(); } - public boolean contains(Object o) { return m.containsKey(o); } - public boolean remove(Object o) { return m.remove(o) != null; } - public void clear() { m.clear(); } - public E lower(E e) { return m.lowerKey(e); } - public E floor(E e) { return m.floorKey(e); } - public E ceiling(E e) { return m.ceilingKey(e); } - public E higher(E e) { return m.higherKey(e); } - public Comparator comparator() { return m.comparator(); } - public E first() { return m.firstKey(); } - public E last() { return m.lastKey(); } - public E pollFirst() { - Map.Entry e = m.pollFirstEntry(); - return e == null? null : e.getKey(); - } - public E pollLast() { - Map.Entry e = m.pollLastEntry(); - return e == null? null : e.getKey(); - } - public Iterator iterator() { - if (m instanceof ConcurrentSkipListMap) - return ((ConcurrentSkipListMap)m).keyIterator(); - else - return ((ConcurrentSkipListMap.SubMap)m).keyIterator(); - } - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof Set)) - return false; - Collection c = (Collection) o; - try { - return containsAll(c) && c.containsAll(this); - } catch (ClassCastException unused) { - return false; - } catch (NullPointerException unused) { - return false; - } - } - public Object[] toArray() { return toList(this).toArray(); } - public T[] toArray(T[] a) { return toList(this).toArray(a); } - public Iterator descendingIterator() { - return descendingSet().iterator(); - } - public NavigableSet subSet(E fromElement, - boolean fromInclusive, - E toElement, - boolean toInclusive) { - return new KeySet(m.subMap(fromElement, fromInclusive, - toElement, toInclusive)); - } - public NavigableSet headSet(E toElement, boolean inclusive) { - return new KeySet(m.headMap(toElement, inclusive)); - } - public NavigableSet tailSet(E fromElement, boolean inclusive) { - return new KeySet(m.tailMap(fromElement, inclusive)); - } - public NavigableSet subSet(E fromElement, E toElement) { - return subSet(fromElement, true, toElement, false); - } - public NavigableSet headSet(E toElement) { - return headSet(toElement, false); - } - public NavigableSet tailSet(E fromElement) { - return tailSet(fromElement, true); - } - public NavigableSet descendingSet() { - return new KeySet(m.descendingMap()); - } - } - - static final class Values extends AbstractCollection { - private final ConcurrentNavigableMap m; - Values(ConcurrentNavigableMap map) { - m = map; - } - public Iterator iterator() { - if (m instanceof ConcurrentSkipListMap) - return ((ConcurrentSkipListMap)m).valueIterator(); - else - return ((SubMap)m).valueIterator(); - } - public boolean isEmpty() { - return m.isEmpty(); - } - public int size() { - return m.size(); - } - public boolean contains(Object o) { - return m.containsValue(o); - } - public void clear() { - m.clear(); - } - public Object[] toArray() { return toList(this).toArray(); } - public T[] toArray(T[] a) { return toList(this).toArray(a); } - } - - static final class EntrySet extends AbstractSet> { - private final ConcurrentNavigableMap m; - EntrySet(ConcurrentNavigableMap map) { - m = map; - } - - public Iterator> iterator() { - if (m instanceof ConcurrentSkipListMap) - return ((ConcurrentSkipListMap)m).entryIterator(); - else - return ((SubMap)m).entryIterator(); - } - - public boolean contains(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry)o; - V1 v = m.get(e.getKey()); - return v != null && v.equals(e.getValue()); - } - public boolean remove(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry)o; - return m.remove(e.getKey(), - e.getValue()); - } - public boolean isEmpty() { - return m.isEmpty(); - } - public int size() { - return m.size(); - } - public void clear() { - m.clear(); - } - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof Set)) - return false; - Collection c = (Collection) o; - try { - return containsAll(c) && c.containsAll(this); - } catch (ClassCastException unused) { - return false; - } catch (NullPointerException unused) { - return false; - } - } - public Object[] toArray() { return toList(this).toArray(); } - public T[] toArray(T[] a) { return toList(this).toArray(a); } - } - - /** - * Submaps returned by {@link ConcurrentSkipListMap} submap operations - * represent a subrange of mappings of their underlying - * maps. Instances of this class support all methods of their - * underlying maps, differing in that mappings outside their range are - * ignored, and attempts to add mappings outside their ranges result - * in {@link IllegalArgumentException}. Instances of this class are - * constructed only using the subMap, headMap, and - * tailMap methods of their underlying maps. - * - * @serial include - */ - static final class SubMap extends AbstractMap - implements ConcurrentNavigableMap, Cloneable, - java.io.Serializable { - private static final long serialVersionUID = -7647078645895051609L; - - /** Underlying map */ - private final ConcurrentSkipListMap m; - /** lower bound key, or null if from start */ - private final K lo; - /** upper bound key, or null if to end */ - private final K hi; - /** inclusion flag for lo */ - private final boolean loInclusive; - /** inclusion flag for hi */ - private final boolean hiInclusive; - /** direction */ - private final boolean isDescending; - - // Lazily initialized view holders - private transient KeySet keySetView; - private transient Set> entrySetView; - private transient Collection valuesView; - - /** - * Creates a new submap, initializing all fields - */ - SubMap(ConcurrentSkipListMap map, - K fromKey, boolean fromInclusive, - K toKey, boolean toInclusive, - boolean isDescending) { - if (fromKey != null && toKey != null && - map.compare(fromKey, toKey) > 0) - throw new IllegalArgumentException("inconsistent range"); - this.m = map; - this.lo = fromKey; - this.hi = toKey; - this.loInclusive = fromInclusive; - this.hiInclusive = toInclusive; - this.isDescending = isDescending; - } - - /* ---------------- Utilities -------------- */ - - private boolean tooLow(K key) { - if (lo != null) { - int c = m.compare(key, lo); - if (c < 0 || (c == 0 && !loInclusive)) - return true; - } - return false; - } - - private boolean tooHigh(K key) { - if (hi != null) { - int c = m.compare(key, hi); - if (c > 0 || (c == 0 && !hiInclusive)) - return true; - } - return false; - } - - private boolean inBounds(K key) { - return !tooLow(key) && !tooHigh(key); - } - - private void checkKeyBounds(K key) throws IllegalArgumentException { - if (key == null) - throw new NullPointerException(); - if (!inBounds(key)) - throw new IllegalArgumentException("key out of range"); - } - - /** - * Returns true if node key is less than upper bound of range - */ - private boolean isBeforeEnd(ConcurrentSkipListMap.Node n) { - if (n == null) - return false; - if (hi == null) - return true; - K k = n.key; - if (k == null) // pass by markers and headers - return true; - int c = m.compare(k, hi); - if (c > 0 || (c == 0 && !hiInclusive)) - return false; - return true; - } - - /** - * Returns lowest node. This node might not be in range, so - * most usages need to check bounds - */ - private ConcurrentSkipListMap.Node loNode() { - if (lo == null) - return m.findFirst(); - else if (loInclusive) - return m.findNear(lo, m.GT|m.EQ); - else - return m.findNear(lo, m.GT); - } - - /** - * Returns highest node. This node might not be in range, so - * most usages need to check bounds - */ - private ConcurrentSkipListMap.Node hiNode() { - if (hi == null) - return m.findLast(); - else if (hiInclusive) - return m.findNear(hi, m.LT|m.EQ); - else - return m.findNear(hi, m.LT); - } - - /** - * Returns lowest absolute key (ignoring directonality) - */ - private K lowestKey() { - ConcurrentSkipListMap.Node n = loNode(); - if (isBeforeEnd(n)) - return n.key; - else - throw new NoSuchElementException(); - } - - /** - * Returns highest absolute key (ignoring directonality) - */ - private K highestKey() { - ConcurrentSkipListMap.Node n = hiNode(); - if (n != null) { - K last = n.key; - if (inBounds(last)) - return last; - } - throw new NoSuchElementException(); - } - - private Map.Entry lowestEntry() { - for (;;) { - ConcurrentSkipListMap.Node n = loNode(); - if (!isBeforeEnd(n)) - return null; - Map.Entry e = n.createSnapshot(); - if (e != null) - return e; - } - } - - private Map.Entry highestEntry() { - for (;;) { - ConcurrentSkipListMap.Node n = hiNode(); - if (n == null || !inBounds(n.key)) - return null; - Map.Entry e = n.createSnapshot(); - if (e != null) - return e; - } - } - - private Map.Entry removeLowest() { - for (;;) { - Node n = loNode(); - if (n == null) - return null; - K k = n.key; - if (!inBounds(k)) - return null; - V v = m.doRemove(k, null); - if (v != null) - return new AbstractMap.SimpleImmutableEntry(k, v); - } - } - - private Map.Entry removeHighest() { - for (;;) { - Node n = hiNode(); - if (n == null) - return null; - K k = n.key; - if (!inBounds(k)) - return null; - V v = m.doRemove(k, null); - if (v != null) - return new AbstractMap.SimpleImmutableEntry(k, v); - } - } - - /** - * Submap version of ConcurrentSkipListMap.getNearEntry - */ - private Map.Entry getNearEntry(K key, int rel) { - if (isDescending) { // adjust relation for direction - if ((rel & m.LT) == 0) - rel |= m.LT; - else - rel &= ~m.LT; - } - if (tooLow(key)) - return ((rel & m.LT) != 0)? null : lowestEntry(); - if (tooHigh(key)) - return ((rel & m.LT) != 0)? highestEntry() : null; - for (;;) { - Node n = m.findNear(key, rel); - if (n == null || !inBounds(n.key)) - return null; - K k = n.key; - V v = n.getValidValue(); - if (v != null) - return new AbstractMap.SimpleImmutableEntry(k, v); - } - } - - // Almost the same as getNearEntry, except for keys - private K getNearKey(K key, int rel) { - if (isDescending) { // adjust relation for direction - if ((rel & m.LT) == 0) - rel |= m.LT; - else - rel &= ~m.LT; - } - if (tooLow(key)) { - if ((rel & m.LT) == 0) { - ConcurrentSkipListMap.Node n = loNode(); - if (isBeforeEnd(n)) - return n.key; - } - return null; - } - if (tooHigh(key)) { - if ((rel & m.LT) != 0) { - ConcurrentSkipListMap.Node n = hiNode(); - if (n != null) { - K last = n.key; - if (inBounds(last)) - return last; - } - } - return null; - } - for (;;) { - Node n = m.findNear(key, rel); - if (n == null || !inBounds(n.key)) - return null; - K k = n.key; - V v = n.getValidValue(); - if (v != null) - return k; - } - } - - /* ---------------- Map API methods -------------- */ - - public boolean containsKey(Object key) { - if (key == null) throw new NullPointerException(); - K k = (K)key; - return inBounds(k) && m.containsKey(k); - } - - public V get(Object key) { - if (key == null) throw new NullPointerException(); - K k = (K)key; - return ((!inBounds(k)) ? null : m.get(k)); - } - - public V put(K key, V value) { - checkKeyBounds(key); - return m.put(key, value); - } - - public V remove(Object key) { - K k = (K)key; - return (!inBounds(k))? null : m.remove(k); - } - - public int size() { - long count = 0; - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); - n = n.next_) { - if (n.getValidValue() != null) - ++count; - } - return count >= Integer.MAX_VALUE? Integer.MAX_VALUE : (int)count; - } - - public boolean isEmpty() { - return !isBeforeEnd(loNode()); - } - - public boolean containsValue(Object value) { - if (value == null) - throw new NullPointerException(); - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); - n = n.next_) { - V v = n.getValidValue(); - if (v != null && value.equals(v)) - return true; - } - return false; - } - - public void clear() { - for (ConcurrentSkipListMap.Node n = loNode(); - isBeforeEnd(n); - n = n.next_) { - if (n.getValidValue() != null) - m.remove(n.key); - } - } - - /* ---------------- ConcurrentMap API methods -------------- */ - - public V putIfAbsent(K key, V value) { - checkKeyBounds(key); - return m.putIfAbsent(key, value); - } - - public boolean remove(Object key, Object value) { - K k = (K)key; - return inBounds(k) && m.remove(k, value); - } - - public boolean replace(K key, V oldValue, V newValue) { - checkKeyBounds(key); - return m.replace(key, oldValue, newValue); - } - - public V replace(K key, V value) { - checkKeyBounds(key); - return m.replace(key, value); - } - - /* ---------------- SortedMap API methods -------------- */ - - public Comparator comparator() { - Comparator cmp = m.comparator(); - if (isDescending) - return Collections.reverseOrder(cmp); - else - return cmp; - } - - /** - * Utility to create submaps, where given bounds override - * unbounded(null) ones and/or are checked against bounded ones. - */ - private SubMap newSubMap(K fromKey, - boolean fromInclusive, - K toKey, - boolean toInclusive) { - if (isDescending) { // flip senses - K tk = fromKey; - fromKey = toKey; - toKey = tk; - boolean ti = fromInclusive; - fromInclusive = toInclusive; - toInclusive = ti; - } - if (lo != null) { - if (fromKey == null) { - fromKey = lo; - fromInclusive = loInclusive; - } - else { - int c = m.compare(fromKey, lo); - if (c < 0 || (c == 0 && !loInclusive && fromInclusive)) - throw new IllegalArgumentException("key out of range"); - } - } - if (hi != null) { - if (toKey == null) { - toKey = hi; - toInclusive = hiInclusive; - } - else { - int c = m.compare(toKey, hi); - if (c > 0 || (c == 0 && !hiInclusive && toInclusive)) - throw new IllegalArgumentException("key out of range"); - } - } - return new SubMap(m, fromKey, fromInclusive, - toKey, toInclusive, isDescending); - } - - public SubMap subMap(K fromKey, - boolean fromInclusive, - K toKey, - boolean toInclusive) { - if (fromKey == null || toKey == null) - throw new NullPointerException(); - return newSubMap(fromKey, fromInclusive, toKey, toInclusive); - } - - public SubMap headMap(K toKey, - boolean inclusive) { - if (toKey == null) - throw new NullPointerException(); - return newSubMap(null, false, toKey, inclusive); - } - - public SubMap tailMap(K fromKey, - boolean inclusive) { - if (fromKey == null) - throw new NullPointerException(); - return newSubMap(fromKey, inclusive, null, false); - } - - public SubMap subMap(K fromKey, K toKey) { - return subMap(fromKey, true, toKey, false); - } - - public SubMap headMap(K toKey) { - return headMap(toKey, false); - } - - public SubMap tailMap(K fromKey) { - return tailMap(fromKey, true); - } - - public SubMap descendingMap() { - return new SubMap(m, lo, loInclusive, - hi, hiInclusive, !isDescending); - } - - /* ---------------- Relational methods -------------- */ - - public Map.Entry ceilingEntry(K key) { - return getNearEntry(key, (m.GT|m.EQ)); - } - - public K ceilingKey(K key) { - return getNearKey(key, (m.GT|m.EQ)); - } - - public Map.Entry lowerEntry(K key) { - return getNearEntry(key, (m.LT)); - } - - public K lowerKey(K key) { - return getNearKey(key, (m.LT)); - } - - public Map.Entry floorEntry(K key) { - return getNearEntry(key, (m.LT|m.EQ)); - } - - public K floorKey(K key) { - return getNearKey(key, (m.LT|m.EQ)); - } - - public Map.Entry higherEntry(K key) { - return getNearEntry(key, (m.GT)); - } - - public K higherKey(K key) { - return getNearKey(key, (m.GT)); - } - - public K firstKey() { - return isDescending? highestKey() : lowestKey(); - } - - public K lastKey() { - return isDescending? lowestKey() : highestKey(); - } - - public Map.Entry firstEntry() { - return isDescending? highestEntry() : lowestEntry(); - } - - public Map.Entry lastEntry() { - return isDescending? lowestEntry() : highestEntry(); - } - - public Map.Entry pollFirstEntry() { - return isDescending? removeHighest() : removeLowest(); - } - - public Map.Entry pollLastEntry() { - return isDescending? removeLowest() : removeHighest(); - } - - /* ---------------- Submap Views -------------- */ - - public NavigableSet keySet() { - KeySet ks = keySetView; - return (ks != null) ? ks : (keySetView = new KeySet(this)); - } - - public NavigableSet navigableKeySet() { - KeySet ks = keySetView; - return (ks != null) ? ks : (keySetView = new KeySet(this)); - } - - public Collection values() { - Collection vs = valuesView; - return (vs != null) ? vs : (valuesView = new Values(this)); - } - - public Set> entrySet() { - Set> es = entrySetView; - return (es != null) ? es : (entrySetView = new EntrySet(this)); - } - - public NavigableSet descendingKeySet() { - return descendingMap().navigableKeySet(); - } - - Iterator keyIterator() { - return new SubMapKeyIterator(); - } - - Iterator valueIterator() { - return new SubMapValueIterator(); - } - - Iterator> entryIterator() { - return new SubMapEntryIterator(); - } - - /** - * Variant of main Iter class to traverse through submaps. - */ - abstract class SubMapIter implements Iterator { - /** the last node returned by next() */ - Node lastReturned; - /** the next node to return from next(); */ - Node next; - /** Cache of next value field to maintain weak consistency */ - V nextValue; - - SubMapIter() { - for (;;) { - next = isDescending ? hiNode() : loNode(); - if (next == null) - break; - Object x = next.value; - if (x != null && x != next) { - if (! inBounds(next.key)) - next = null; - else - nextValue = (V) x; - break; - } - } - } - - public final boolean hasNext() { - return next != null; - } - - final void advance() { - if (next == null) - throw new NoSuchElementException(); - lastReturned = next; - if (isDescending) - descend(); - else - ascend(); - } - - private void ascend() { - for (;;) { - next = next.next_; - if (next == null) - break; - Object x = next.value; - if (x != null && x != next) { - if (tooHigh(next.key)) - next = null; - else - nextValue = (V) x; - break; - } - } - } - - private void descend() { - for (;;) { - next = m.findNear(lastReturned.key, LT); - if (next == null) - break; - Object x = next.value; - if (x != null && x != next) { - if (tooLow(next.key)) - next = null; - else - nextValue = (V) x; - break; - } - } - } - - public void remove() { - Node l = lastReturned; - if (l == null) - throw new IllegalStateException(); - m.remove(l.key); - lastReturned = null; - } - - } - - final class SubMapValueIterator extends SubMapIter { - public V next() { - V v = nextValue; - advance(); - return v; - } - } - - final class SubMapKeyIterator extends SubMapIter { - public K next() { - Node n = next; - advance(); - return n.key; - } - } - - final class SubMapEntryIterator extends SubMapIter> { - public Map.Entry next() { - Node n = next; - V v = nextValue; - advance(); - return new AbstractMap.SimpleImmutableEntry(n.key, v); - } - } + super(m); +// this.comparator = m.comparator(); +// initialize(); +// buildFromSorted(m); + putAll(m); } +// +// /** +// * Returns a shallow copy of this ConcurrentSkipListMap +// * instance. (The keys and values themselves are not cloned.) +// * +// * @return a shallow copy of this map +// */ +// public ConcurrentSkipListMap clone() { +// ConcurrentSkipListMap clone = null; +// try { +// clone = (ConcurrentSkipListMap) super.clone(); +// } catch (CloneNotSupportedException e) { +// throw new InternalError(); +// } +// +// clone.initialize(); +// clone.buildFromSorted(this); +// return clone; +// } +// +// /** +// * Streamlined bulk insertion to initialize from elements of +// * given sorted map. Call only from constructor or clone +// * method. +// */ +// private void buildFromSorted(SortedMap map) { +// if (map == null) +// throw new NullPointerException(); +// +// HeadIndex h = head; +// Node basepred = h.node; +// +// // Track the current rightmost node at each level. Uses an +// // ArrayList to avoid committing to initial or maximum level. +// ArrayList> preds = new ArrayList>(); +// +// // initialize +// for (int i = 0; i <= h.level; ++i) +// preds.add(null); +// Index q = h; +// for (int i = h.level; i > 0; --i) { +// preds.set(i, q); +// q = q.down; +// } +// +// Iterator> it = +// map.entrySet().iterator(); +// while (it.hasNext()) { +// Map.Entry e = it.next(); +// int j = randomLevel(); +// if (j > h.level) j = h.level + 1; +// K k = e.getKey(); +// V v = e.getValue(); +// if (k == null || v == null) +// throw new NullPointerException(); +// Node z = new Node(k, v, null); +// basepred.next_ = z; +// basepred = z; +// if (j > 0) { +// Index idx = null; +// for (int i = 1; i <= j; ++i) { +// idx = new Index(z, idx, null); +// if (i > h.level) +// h = new HeadIndex(h.node, h, idx, i); +// +// if (i < preds.size()) { +// preds.get(i).right = idx; +// preds.set(i, idx); +// } else +// preds.add(idx); +// } +// } +// } +// head = h; +// } +// +// /* ---------------- Serialization -------------- */ +// +// /** +// * Save the state of this map to a stream. +// * +// * @serialData The key (Object) and value (Object) for each +// * key-value mapping represented by the map, followed by +// * null. The key-value mappings are emitted in key-order +// * (as determined by the Comparator, or by the keys' natural +// * ordering if no Comparator). +// */ +// private void writeObject(java.io.ObjectOutputStream s) +// throws java.io.IOException { +// // Write out the Comparator and any hidden stuff +// s.defaultWriteObject(); +// +// // Write out keys and values (alternating) +// for (Node n = findFirst(); n != null; n = n.next_) { +// V v = n.getValidValue(); +// if (v != null) { +// s.writeObject(n.key); +// s.writeObject(v); +// } +// } +// s.writeObject(null); +// } +// +// /** +// * Reconstitute the map from a stream. +// */ +// private void readObject(final java.io.ObjectInputStream s) +// throws java.io.IOException, ClassNotFoundException { +// // Read in the Comparator and any hidden stuff +// s.defaultReadObject(); +// // Reset transients +// initialize(); +// +// /* +// * This is nearly identical to buildFromSorted, but is +// * distinct because readObject calls can't be nicely adapted +// * as the kind of iterator needed by buildFromSorted. (They +// * can be, but doing so requires type cheats and/or creation +// * of adaptor classes.) It is simpler to just adapt the code. +// */ +// +// HeadIndex h = head; +// Node basepred = h.node; +// ArrayList> preds = new ArrayList>(); +// for (int i = 0; i <= h.level; ++i) +// preds.add(null); +// Index q = h; +// for (int i = h.level; i > 0; --i) { +// preds.set(i, q); +// q = q.down; +// } +// +// for (;;) { +// Object k = s.readObject(); +// if (k == null) +// break; +// Object v = s.readObject(); +// if (v == null) +// throw new NullPointerException(); +// K key = (K) k; +// V val = (V) v; +// int j = randomLevel(); +// if (j > h.level) j = h.level + 1; +// Node z = new Node(key, val, null); +// basepred.next_ = z; +// basepred = z; +// if (j > 0) { +// Index idx = null; +// for (int i = 1; i <= j; ++i) { +// idx = new Index(z, idx, null); +// if (i > h.level) +// h = new HeadIndex(h.node, h, idx, i); +// +// if (i < preds.size()) { +// preds.get(i).right = idx; +// preds.set(i, idx); +// } else +// preds.add(idx); +// } +// } +// } +// head = h; +// } +// +// /* ------ Map API methods ------ */ +// +// /** +// * Returns true if this map contains a mapping for the specified +// * key. +// * +// * @param key key whose presence in this map is to be tested +// * @return true if this map contains a mapping for the specified key +// * @throws ClassCastException if the specified key cannot be compared +// * with the keys currently in the map +// * @throws NullPointerException if the specified key is null +// */ +// public boolean containsKey(Object key) { +// return doGet(key) != null; +// } +// +// /** +// * Returns the value to which the specified key is mapped, +// * or {@code null} if this map contains no mapping for the key. +// * +// *

More formally, if this map contains a mapping from a key +// * {@code k} to a value {@code v} such that {@code key} compares +// * equal to {@code k} according to the map's ordering, then this +// * method returns {@code v}; otherwise it returns {@code null}. +// * (There can be at most one such mapping.) +// * +// * @throws ClassCastException if the specified key cannot be compared +// * with the keys currently in the map +// * @throws NullPointerException if the specified key is null +// */ +// public V get(Object key) { +// return doGet(key); +// } +// +// /** +// * Associates the specified value with the specified key in this map. +// * If the map previously contained a mapping for the key, the old +// * value is replaced. +// * +// * @param key key with which the specified value is to be associated +// * @param value value to be associated with the specified key +// * @return the previous value associated with the specified key, or +// * null if there was no mapping for the key +// * @throws ClassCastException if the specified key cannot be compared +// * with the keys currently in the map +// * @throws NullPointerException if the specified key or value is null +// */ +// public V put(K key, V value) { +// if (value == null) +// throw new NullPointerException(); +// return doPut(key, value, false); +// } +// +// /** +// * Removes the mapping for the specified key from this map if present. +// * +// * @param key key for which mapping should be removed +// * @return the previous value associated with the specified key, or +// * null if there was no mapping for the key +// * @throws ClassCastException if the specified key cannot be compared +// * with the keys currently in the map +// * @throws NullPointerException if the specified key is null +// */ +// public V remove(Object key) { +// return doRemove(key, null); +// } +// +// /** +// * Returns true if this map maps one or more keys to the +// * specified value. This operation requires time linear in the +// * map size. +// * +// * @param value value whose presence in this map is to be tested +// * @return true if a mapping to value exists; +// * false otherwise +// * @throws NullPointerException if the specified value is null +// */ +// public boolean containsValue(Object value) { +// if (value == null) +// throw new NullPointerException(); +// for (Node n = findFirst(); n != null; n = n.next_) { +// V v = n.getValidValue(); +// if (v != null && value.equals(v)) +// return true; +// } +// return false; +// } +// +// /** +// * Returns the number of key-value mappings in this map. If this map +// * contains more than Integer.MAX_VALUE elements, it +// * returns Integer.MAX_VALUE. +// * +// *

Beware that, unlike in most collections, this method is +// * NOT a constant-time operation. Because of the +// * asynchronous nature of these maps, determining the current +// * number of elements requires traversing them all to count them. +// * Additionally, it is possible for the size to change during +// * execution of this method, in which case the returned result +// * will be inaccurate. Thus, this method is typically not very +// * useful in concurrent applications. +// * +// * @return the number of elements in this map +// */ +// public int size() { +// long count = 0; +// for (Node n = findFirst(); n != null; n = n.next_) { +// if (n.getValidValue() != null) +// ++count; +// } +// return (count >= Integer.MAX_VALUE)? Integer.MAX_VALUE : (int)count; +// } +// +// /** +// * Returns true if this map contains no key-value mappings. +// * @return true if this map contains no key-value mappings +// */ +// public boolean isEmpty() { +// return findFirst() == null; +// } +// +// /** +// * Removes all of the mappings from this map. +// */ +// public void clear() { +// initialize(); +// } +// +// /* ---------------- View methods -------------- */ +// +// /* +// * Note: Lazy initialization works for views because view classes +// * are stateless/immutable so it doesn't matter wrt correctness if +// * more than one is created (which will only rarely happen). Even +// * so, the following idiom conservatively ensures that the method +// * returns the one it created if it does so, not one created by +// * another racing thread. +// */ +// +// /** +// * Returns a {@link NavigableSet} view of the keys contained in this map. +// * The set's iterator returns the keys in ascending order. +// * The set is backed by the map, so changes to the map are +// * reflected in the set, and vice-versa. The set supports element +// * removal, which removes the corresponding mapping from the map, +// * via the {@code Iterator.remove}, {@code Set.remove}, +// * {@code removeAll}, {@code retainAll}, and {@code clear} +// * operations. It does not support the {@code add} or {@code addAll} +// * operations. +// * +// *

The view's {@code iterator} is a "weakly consistent" iterator +// * that will never throw {@link ConcurrentModificationException}, +// * and guarantees to traverse elements as they existed upon +// * construction of the iterator, and may (but is not guaranteed to) +// * reflect any modifications subsequent to construction. +// * +// *

This method is equivalent to method {@code navigableKeySet}. +// * +// * @return a navigable set view of the keys in this map +// */ + + +// TODO + +// public NavigableSet keySet() { +// return super.keySet(); +// KeySet ks = keySet; +// return (ks != null) ? ks : (keySet = new KeySet(this)); +// } +// +// public NavigableSet navigableKeySet() { +// KeySet ks = keySet; +// return (ks != null) ? ks : (keySet = new KeySet(this)); +// } +// +// /** +// * Returns a {@link Collection} view of the values contained in this map. +// * The collection's iterator returns the values in ascending order +// * of the corresponding keys. +// * The collection is backed by the map, so changes to the map are +// * reflected in the collection, and vice-versa. The collection +// * supports element removal, which removes the corresponding +// * mapping from the map, via the Iterator.remove, +// * Collection.remove, removeAll, +// * retainAll and clear operations. It does not +// * support the add or addAll operations. +// * +// *

The view's iterator is a "weakly consistent" iterator +// * that will never throw {@link ConcurrentModificationException}, +// * and guarantees to traverse elements as they existed upon +// * construction of the iterator, and may (but is not guaranteed to) +// * reflect any modifications subsequent to construction. +// */ +// public Collection values() { +// Values vs = values; +// return (vs != null) ? vs : (values = new Values(this)); +// } +// +// /** +// * Returns a {@link Set} view of the mappings contained in this map. +// * The set's iterator returns the entries in ascending key order. +// * The set is backed by the map, so changes to the map are +// * reflected in the set, and vice-versa. The set supports element +// * removal, which removes the corresponding mapping from the map, +// * via the Iterator.remove, Set.remove, +// * removeAll, retainAll and clear +// * operations. It does not support the add or +// * addAll operations. +// * +// *

The view's iterator is a "weakly consistent" iterator +// * that will never throw {@link ConcurrentModificationException}, +// * and guarantees to traverse elements as they existed upon +// * construction of the iterator, and may (but is not guaranteed to) +// * reflect any modifications subsequent to construction. +// * +// *

The Map.Entry elements returned by +// * iterator.next() do not support the +// * setValue operation. +// * +// * @return a set view of the mappings contained in this map, +// * sorted in ascending key order +// */ +// public Set> entrySet() { +// EntrySet es = entrySet; +// return (es != null) ? es : (entrySet = new EntrySet(this)); +// } +// +// public ConcurrentNavigableMap descendingMap() { +// ConcurrentNavigableMap dm = descendingMap; +// return (dm != null) ? dm : (descendingMap = new SubMap +// (this, null, false, null, false, true)); +// } +// +// public NavigableSet descendingKeySet() { +// return descendingMap().navigableKeySet(); +// } +// +// /* ---------------- AbstractMap Overrides -------------- */ +// +// /** +// * Compares the specified object with this map for equality. +// * Returns true if the given object is also a map and the +// * two maps represent the same mappings. More formally, two maps +// * m1 and m2 represent the same mappings if +// * m1.entrySet().equals(m2.entrySet()). This +// * operation may return misleading results if either map is +// * concurrently modified during execution of this method. +// * +// * @param o object to be compared for equality with this map +// * @return true if the specified object is equal to this map +// */ +// public boolean equals(Object o) { +// if (o == this) +// return true; +// if (!(o instanceof Map)) +// return false; +// Map m = (Map) o; +// try { +// for (Map.Entry e : this.entrySet()) +// if (! e.getValue().equals(m.get(e.getKey()))) +// return false; +// for (Map.Entry e : m.entrySet()) { +// Object k = e.getKey(); +// Object v = e.getValue(); +// if (k == null || v == null || !v.equals(get(k))) +// return false; +// } +// return true; +// } catch (ClassCastException unused) { +// return false; +// } catch (NullPointerException unused) { +// return false; +// } +// } +// +// /* ------ ConcurrentMap API methods ------ */ +// +// /** +// * {@inheritDoc} +// * +// * @return the previous value associated with the specified key, +// * or null if there was no mapping for the key +// * @throws ClassCastException if the specified key cannot be compared +// * with the keys currently in the map +// * @throws NullPointerException if the specified key or value is null +// */ +// public V putIfAbsent(K key, V value) { +// if (value == null) +// throw new NullPointerException(); +// return doPut(key, value, true); +// } +// +// /** +// * {@inheritDoc} +// * +// * @throws ClassCastException if the specified key cannot be compared +// * with the keys currently in the map +// * @throws NullPointerException if the specified key is null +// */ +// public boolean remove(Object key, Object value) { +// if (key == null) +// throw new NullPointerException(); +// if (value == null) +// return false; +// return doRemove(key, value) != null; +// } +// +// /** +// * {@inheritDoc} +// * +// * @throws ClassCastException if the specified key cannot be compared +// * with the keys currently in the map +// * @throws NullPointerException if any of the arguments are null +// */ +// public boolean replace(K key, V oldValue, V newValue) { +// if (oldValue == null || newValue == null) +// throw new NullPointerException(); +// Comparable k = comparable(key); +// for (;;) { +// Node n = findNode(k); +// if (n == null) +// return false; +// Object v = n.value; +// if (v != null) { +// if (!oldValue.equals(v)) +// return false; +// if (n.casValue(v, newValue)) +// return true; +// } +// } +// } +// +// /** +// * {@inheritDoc} +// * +// * @return the previous value associated with the specified key, +// * or null if there was no mapping for the key +// * @throws ClassCastException if the specified key cannot be compared +// * with the keys currently in the map +// * @throws NullPointerException if the specified key or value is null +// */ +// public V replace(K key, V value) { +// if (value == null) +// throw new NullPointerException(); +// Comparable k = comparable(key); +// for (;;) { +// Node n = findNode(k); +// if (n == null) +// return null; +// Object v = n.value; +// if (v != null && n.casValue(v, value)) +// return (V)v; +// } +// } +// +// /* ------ SortedMap API methods ------ */ +// +// public Comparator comparator() { +// return comparator; +// } +// +// /** +// * @throws NoSuchElementException {@inheritDoc} +// */ +// public K firstKey() { +// Node n = findFirst(); +// if (n == null) +// throw new NoSuchElementException(); +// return n.key; +// } +// +// /** +// * @throws NoSuchElementException {@inheritDoc} +// */ +// public K lastKey() { +// Node n = findLast(); +// if (n == null) +// throw new NoSuchElementException(); +// return n.key; +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code fromKey} or {@code toKey} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public ConcurrentNavigableMap subMap(K fromKey, +// boolean fromInclusive, +// K toKey, +// boolean toInclusive) { +// if (fromKey == null || toKey == null) +// throw new NullPointerException(); +// return new SubMap +// (this, fromKey, fromInclusive, toKey, toInclusive, false); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code toKey} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public ConcurrentNavigableMap headMap(K toKey, +// boolean inclusive) { +// if (toKey == null) +// throw new NullPointerException(); +// return new SubMap +// (this, null, false, toKey, inclusive, false); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code fromKey} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public ConcurrentNavigableMap tailMap(K fromKey, +// boolean inclusive) { +// if (fromKey == null) +// throw new NullPointerException(); +// return new SubMap +// (this, fromKey, inclusive, null, false, false); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code fromKey} or {@code toKey} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public ConcurrentNavigableMap subMap(K fromKey, K toKey) { +// return subMap(fromKey, true, toKey, false); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code toKey} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public ConcurrentNavigableMap headMap(K toKey) { +// return headMap(toKey, false); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code fromKey} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public ConcurrentNavigableMap tailMap(K fromKey) { +// return tailMap(fromKey, true); +// } +// +// /* ---------------- Relational operations -------------- */ +// +// /** +// * Returns a key-value mapping associated with the greatest key +// * strictly less than the given key, or null if there is +// * no such key. The returned entry does not support the +// * Entry.setValue method. +// * +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// public Map.Entry lowerEntry(K key) { +// return getNear(key, LT); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// public K lowerKey(K key) { +// Node n = findNear(key, LT); +// return (n == null)? null : n.key; +// } +// +// /** +// * Returns a key-value mapping associated with the greatest key +// * less than or equal to the given key, or null if there +// * is no such key. The returned entry does not support +// * the Entry.setValue method. +// * +// * @param key the key +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// public Map.Entry floorEntry(K key) { +// return getNear(key, LT|EQ); +// } +// +// /** +// * @param key the key +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// public K floorKey(K key) { +// Node n = findNear(key, LT|EQ); +// return (n == null)? null : n.key; +// } +// +// /** +// * Returns a key-value mapping associated with the least key +// * greater than or equal to the given key, or null if +// * there is no such entry. The returned entry does not +// * support the Entry.setValue method. +// * +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// public Map.Entry ceilingEntry(K key) { +// return getNear(key, GT|EQ); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// public K ceilingKey(K key) { +// Node n = findNear(key, GT|EQ); +// return (n == null)? null : n.key; +// } +// +// /** +// * Returns a key-value mapping associated with the least key +// * strictly greater than the given key, or null if there +// * is no such key. The returned entry does not support +// * the Entry.setValue method. +// * +// * @param key the key +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// public Map.Entry higherEntry(K key) { +// return getNear(key, GT); +// } +// +// /** +// * @param key the key +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified key is null +// */ +// public K higherKey(K key) { +// Node n = findNear(key, GT); +// return (n == null)? null : n.key; +// } +// +// /** +// * Returns a key-value mapping associated with the least +// * key in this map, or null if the map is empty. +// * The returned entry does not support +// * the Entry.setValue method. +// */ +// public Map.Entry firstEntry() { +// for (;;) { +// Node n = findFirst(); +// if (n == null) +// return null; +// AbstractMap.SimpleImmutableEntry e = n.createSnapshot(); +// if (e != null) +// return e; +// } +// } +// +// /** +// * Returns a key-value mapping associated with the greatest +// * key in this map, or null if the map is empty. +// * The returned entry does not support +// * the Entry.setValue method. +// */ +// public Map.Entry lastEntry() { +// for (;;) { +// Node n = findLast(); +// if (n == null) +// return null; +// AbstractMap.SimpleImmutableEntry e = n.createSnapshot(); +// if (e != null) +// return e; +// } +// } +// +// /** +// * Removes and returns a key-value mapping associated with +// * the least key in this map, or null if the map is empty. +// * The returned entry does not support +// * the Entry.setValue method. +// */ +// public Map.Entry pollFirstEntry() { +// return doRemoveFirstEntry(); +// } +// +// /** +// * Removes and returns a key-value mapping associated with +// * the greatest key in this map, or null if the map is empty. +// * The returned entry does not support +// * the Entry.setValue method. +// */ +// public Map.Entry pollLastEntry() { +// return doRemoveLastEntry(); +// } +// +// +// /* ---------------- Iterators -------------- */ +// +// /** +// * Base of iterator classes: +// */ +// abstract class Iter implements Iterator { +// /** the last node returned by next() */ +// Node lastReturned; +// /** the next node to return from next(); */ +// Node next; +// /** Cache of next value field to maintain weak consistency */ +// V nextValue; +// +// /** Initializes ascending iterator for entire range. */ +// Iter() { +// for (;;) { +// next = findFirst(); +// if (next == null) +// break; +// Object x = next.value; +// if (x != null && x != next) { +// nextValue = (V) x; +// break; +// } +// } +// } +// +// public final boolean hasNext() { +// return next != null; +// } +// +// /** Advances next to higher entry. */ +// final void advance() { +// if (next == null) +// throw new NoSuchElementException(); +// lastReturned = next; +// for (;;) { +// next = next.next_; +// if (next == null) +// break; +// Object x = next.value; +// if (x != null && x != next) { +// nextValue = (V) x; +// break; +// } +// } +// } +// +// public void remove() { +// Node l = lastReturned; +// if (l == null) +// throw new IllegalStateException(); +// // It would not be worth all of the overhead to directly +// // unlink from here. Using remove is fast enough. +// ConcurrentSkipListMap.this.remove(l.key); +// lastReturned = null; +// } +// +// } +// +// final class ValueIterator extends Iter { +// public V next() { +// V v = nextValue; +// advance(); +// return v; +// } +// } +// +// final class KeyIterator extends Iter { +// public K next() { +// Node n = next; +// advance(); +// return n.key; +// } +// } +// +// final class EntryIterator extends Iter> { +// public Map.Entry next() { +// Node n = next; +// V v = nextValue; +// advance(); +// return new AbstractMap.SimpleImmutableEntry(n.key, v); +// } +// } +// +// // Factory methods for iterators needed by ConcurrentSkipListSet etc +// +// Iterator keyIterator() { +// return new KeyIterator(); +// } +// +// Iterator valueIterator() { +// return new ValueIterator(); +// } +// +// Iterator> entryIterator() { +// return new EntryIterator(); +// } +// +// /* ---------------- View Classes -------------- */ +// +// /* +// * View classes are static, delegating to a ConcurrentNavigableMap +// * to allow use by SubMaps, which outweighs the ugliness of +// * needing type-tests for Iterator methods. +// */ +// +// static final List toList(Collection c) { +// // Using size() here would be a pessimization. +// List list = new ArrayList(); +// for (E e : c) +// list.add(e); +// return list; +// } +// +// static final class KeySet extends AbstractSet implements NavigableSet { +// private final ConcurrentNavigableMap m; +// KeySet(ConcurrentNavigableMap map) { m = map; } +// public int size() { return m.size(); } +// public boolean isEmpty() { return m.isEmpty(); } +// public boolean contains(Object o) { return m.containsKey(o); } +// public boolean remove(Object o) { return m.remove(o) != null; } +// public void clear() { m.clear(); } +// public E lower(E e) { return m.lowerKey(e); } +// public E floor(E e) { return m.floorKey(e); } +// public E ceiling(E e) { return m.ceilingKey(e); } +// public E higher(E e) { return m.higherKey(e); } +// public Comparator comparator() { return m.comparator(); } +// public E first() { return m.firstKey(); } +// public E last() { return m.lastKey(); } +// public E pollFirst() { +// Map.Entry e = m.pollFirstEntry(); +// return e == null? null : e.getKey(); +// } +// public E pollLast() { +// Map.Entry e = m.pollLastEntry(); +// return e == null? null : e.getKey(); +// } +// public Iterator iterator() { +// if (m instanceof ConcurrentSkipListMap) +// return ((ConcurrentSkipListMap)m).keyIterator(); +// else +// return ((ConcurrentSkipListMap.SubMap)m).keyIterator(); +// } +// public boolean equals(Object o) { +// if (o == this) +// return true; +// if (!(o instanceof Set)) +// return false; +// Collection c = (Collection) o; +// try { +// return containsAll(c) && c.containsAll(this); +// } catch (ClassCastException unused) { +// return false; +// } catch (NullPointerException unused) { +// return false; +// } +// } +// public Object[] toArray() { return toList(this).toArray(); } +// public T[] toArray(T[] a) { return toList(this).toArray(a); } +// public Iterator descendingIterator() { +// return descendingSet().iterator(); +// } +// public NavigableSet subSet(E fromElement, +// boolean fromInclusive, +// E toElement, +// boolean toInclusive) { +// return new KeySet(m.subMap(fromElement, fromInclusive, +// toElement, toInclusive)); +// } +// public NavigableSet headSet(E toElement, boolean inclusive) { +// return new KeySet(m.headMap(toElement, inclusive)); +// } +// public NavigableSet tailSet(E fromElement, boolean inclusive) { +// return new KeySet(m.tailMap(fromElement, inclusive)); +// } +// public NavigableSet subSet(E fromElement, E toElement) { +// return subSet(fromElement, true, toElement, false); +// } +// public NavigableSet headSet(E toElement) { +// return headSet(toElement, false); +// } +// public NavigableSet tailSet(E fromElement) { +// return tailSet(fromElement, true); +// } +// public NavigableSet descendingSet() { +// return new KeySet(m.descendingMap()); +// } +// } +// +// static final class Values extends AbstractCollection { +// private final ConcurrentNavigableMap m; +// Values(ConcurrentNavigableMap map) { +// m = map; +// } +// public Iterator iterator() { +// if (m instanceof ConcurrentSkipListMap) +// return ((ConcurrentSkipListMap)m).valueIterator(); +// else +// return ((SubMap)m).valueIterator(); +// } +// public boolean isEmpty() { +// return m.isEmpty(); +// } +// public int size() { +// return m.size(); +// } +// public boolean contains(Object o) { +// return m.containsValue(o); +// } +// public void clear() { +// m.clear(); +// } +// public Object[] toArray() { return toList(this).toArray(); } +// public T[] toArray(T[] a) { return toList(this).toArray(a); } +// } +// +// static final class EntrySet extends AbstractSet> { +// private final ConcurrentNavigableMap m; +// EntrySet(ConcurrentNavigableMap map) { +// m = map; +// } +// +// public Iterator> iterator() { +// if (m instanceof ConcurrentSkipListMap) +// return ((ConcurrentSkipListMap)m).entryIterator(); +// else +// return ((SubMap)m).entryIterator(); +// } +// +// public boolean contains(Object o) { +// if (!(o instanceof Map.Entry)) +// return false; +// Map.Entry e = (Map.Entry)o; +// V1 v = m.get(e.getKey()); +// return v != null && v.equals(e.getValue()); +// } +// public boolean remove(Object o) { +// if (!(o instanceof Map.Entry)) +// return false; +// Map.Entry e = (Map.Entry)o; +// return m.remove(e.getKey(), +// e.getValue()); +// } +// public boolean isEmpty() { +// return m.isEmpty(); +// } +// public int size() { +// return m.size(); +// } +// public void clear() { +// m.clear(); +// } +// public boolean equals(Object o) { +// if (o == this) +// return true; +// if (!(o instanceof Set)) +// return false; +// Collection c = (Collection) o; +// try { +// return containsAll(c) && c.containsAll(this); +// } catch (ClassCastException unused) { +// return false; +// } catch (NullPointerException unused) { +// return false; +// } +// } +// public Object[] toArray() { return toList(this).toArray(); } +// public T[] toArray(T[] a) { return toList(this).toArray(a); } +// } +// +// /** +// * Submaps returned by {@link ConcurrentSkipListMap} submap operations +// * represent a subrange of mappings of their underlying +// * maps. Instances of this class support all methods of their +// * underlying maps, differing in that mappings outside their range are +// * ignored, and attempts to add mappings outside their ranges result +// * in {@link IllegalArgumentException}. Instances of this class are +// * constructed only using the subMap, headMap, and +// * tailMap methods of their underlying maps. +// * +// * @serial include +// */ +// static final class SubMap extends AbstractMap +// implements ConcurrentNavigableMap, Cloneable, +// java.io.Serializable { +// private static final long serialVersionUID = -7647078645895051609L; +// +// /** Underlying map */ +// private final ConcurrentSkipListMap m; +// /** lower bound key, or null if from start */ +// private final K lo; +// /** upper bound key, or null if to end */ +// private final K hi; +// /** inclusion flag for lo */ +// private final boolean loInclusive; +// /** inclusion flag for hi */ +// private final boolean hiInclusive; +// /** direction */ +// private final boolean isDescending; +// +// // Lazily initialized view holders +// private transient KeySet keySetView; +// private transient Set> entrySetView; +// private transient Collection valuesView; +// +// /** +// * Creates a new submap, initializing all fields +// */ +// SubMap(ConcurrentSkipListMap map, +// K fromKey, boolean fromInclusive, +// K toKey, boolean toInclusive, +// boolean isDescending) { +// if (fromKey != null && toKey != null && +// map.compare(fromKey, toKey) > 0) +// throw new IllegalArgumentException("inconsistent range"); +// this.m = map; +// this.lo = fromKey; +// this.hi = toKey; +// this.loInclusive = fromInclusive; +// this.hiInclusive = toInclusive; +// this.isDescending = isDescending; +// } +// +// /* ---------------- Utilities -------------- */ +// +// private boolean tooLow(K key) { +// if (lo != null) { +// int c = m.compare(key, lo); +// if (c < 0 || (c == 0 && !loInclusive)) +// return true; +// } +// return false; +// } +// +// private boolean tooHigh(K key) { +// if (hi != null) { +// int c = m.compare(key, hi); +// if (c > 0 || (c == 0 && !hiInclusive)) +// return true; +// } +// return false; +// } +// +// private boolean inBounds(K key) { +// return !tooLow(key) && !tooHigh(key); +// } +// +// private void checkKeyBounds(K key) throws IllegalArgumentException { +// if (key == null) +// throw new NullPointerException(); +// if (!inBounds(key)) +// throw new IllegalArgumentException("key out of range"); +// } +// +// /** +// * Returns true if node key is less than upper bound of range +// */ +// private boolean isBeforeEnd(ConcurrentSkipListMap.Node n) { +// if (n == null) +// return false; +// if (hi == null) +// return true; +// K k = n.key; +// if (k == null) // pass by markers and headers +// return true; +// int c = m.compare(k, hi); +// if (c > 0 || (c == 0 && !hiInclusive)) +// return false; +// return true; +// } +// +// /** +// * Returns lowest node. This node might not be in range, so +// * most usages need to check bounds +// */ +// private ConcurrentSkipListMap.Node loNode() { +// if (lo == null) +// return m.findFirst(); +// else if (loInclusive) +// return m.findNear(lo, m.GT|m.EQ); +// else +// return m.findNear(lo, m.GT); +// } +// +// /** +// * Returns highest node. This node might not be in range, so +// * most usages need to check bounds +// */ +// private ConcurrentSkipListMap.Node hiNode() { +// if (hi == null) +// return m.findLast(); +// else if (hiInclusive) +// return m.findNear(hi, m.LT|m.EQ); +// else +// return m.findNear(hi, m.LT); +// } +// +// /** +// * Returns lowest absolute key (ignoring directonality) +// */ +// private K lowestKey() { +// ConcurrentSkipListMap.Node n = loNode(); +// if (isBeforeEnd(n)) +// return n.key; +// else +// throw new NoSuchElementException(); +// } +// +// /** +// * Returns highest absolute key (ignoring directonality) +// */ +// private K highestKey() { +// ConcurrentSkipListMap.Node n = hiNode(); +// if (n != null) { +// K last = n.key; +// if (inBounds(last)) +// return last; +// } +// throw new NoSuchElementException(); +// } +// +// private Map.Entry lowestEntry() { +// for (;;) { +// ConcurrentSkipListMap.Node n = loNode(); +// if (!isBeforeEnd(n)) +// return null; +// Map.Entry e = n.createSnapshot(); +// if (e != null) +// return e; +// } +// } +// +// private Map.Entry highestEntry() { +// for (;;) { +// ConcurrentSkipListMap.Node n = hiNode(); +// if (n == null || !inBounds(n.key)) +// return null; +// Map.Entry e = n.createSnapshot(); +// if (e != null) +// return e; +// } +// } +// +// private Map.Entry removeLowest() { +// for (;;) { +// Node n = loNode(); +// if (n == null) +// return null; +// K k = n.key; +// if (!inBounds(k)) +// return null; +// V v = m.doRemove(k, null); +// if (v != null) +// return new AbstractMap.SimpleImmutableEntry(k, v); +// } +// } +// +// private Map.Entry removeHighest() { +// for (;;) { +// Node n = hiNode(); +// if (n == null) +// return null; +// K k = n.key; +// if (!inBounds(k)) +// return null; +// V v = m.doRemove(k, null); +// if (v != null) +// return new AbstractMap.SimpleImmutableEntry(k, v); +// } +// } +// +// /** +// * Submap version of ConcurrentSkipListMap.getNearEntry +// */ +// private Map.Entry getNearEntry(K key, int rel) { +// if (isDescending) { // adjust relation for direction +// if ((rel & m.LT) == 0) +// rel |= m.LT; +// else +// rel &= ~m.LT; +// } +// if (tooLow(key)) +// return ((rel & m.LT) != 0)? null : lowestEntry(); +// if (tooHigh(key)) +// return ((rel & m.LT) != 0)? highestEntry() : null; +// for (;;) { +// Node n = m.findNear(key, rel); +// if (n == null || !inBounds(n.key)) +// return null; +// K k = n.key; +// V v = n.getValidValue(); +// if (v != null) +// return new AbstractMap.SimpleImmutableEntry(k, v); +// } +// } +// +// // Almost the same as getNearEntry, except for keys +// private K getNearKey(K key, int rel) { +// if (isDescending) { // adjust relation for direction +// if ((rel & m.LT) == 0) +// rel |= m.LT; +// else +// rel &= ~m.LT; +// } +// if (tooLow(key)) { +// if ((rel & m.LT) == 0) { +// ConcurrentSkipListMap.Node n = loNode(); +// if (isBeforeEnd(n)) +// return n.key; +// } +// return null; +// } +// if (tooHigh(key)) { +// if ((rel & m.LT) != 0) { +// ConcurrentSkipListMap.Node n = hiNode(); +// if (n != null) { +// K last = n.key; +// if (inBounds(last)) +// return last; +// } +// } +// return null; +// } +// for (;;) { +// Node n = m.findNear(key, rel); +// if (n == null || !inBounds(n.key)) +// return null; +// K k = n.key; +// V v = n.getValidValue(); +// if (v != null) +// return k; +// } +// } +// +// /* ---------------- Map API methods -------------- */ +// +// public boolean containsKey(Object key) { +// if (key == null) throw new NullPointerException(); +// K k = (K)key; +// return inBounds(k) && m.containsKey(k); +// } +// +// public V get(Object key) { +// if (key == null) throw new NullPointerException(); +// K k = (K)key; +// return ((!inBounds(k)) ? null : m.get(k)); +// } +// +// public V put(K key, V value) { +// checkKeyBounds(key); +// return m.put(key, value); +// } +// +// public V remove(Object key) { +// K k = (K)key; +// return (!inBounds(k))? null : m.remove(k); +// } +// +// public int size() { +// long count = 0; +// for (ConcurrentSkipListMap.Node n = loNode(); +// isBeforeEnd(n); +// n = n.next_) { +// if (n.getValidValue() != null) +// ++count; +// } +// return count >= Integer.MAX_VALUE? Integer.MAX_VALUE : (int)count; +// } +// +// public boolean isEmpty() { +// return !isBeforeEnd(loNode()); +// } +// +// public boolean containsValue(Object value) { +// if (value == null) +// throw new NullPointerException(); +// for (ConcurrentSkipListMap.Node n = loNode(); +// isBeforeEnd(n); +// n = n.next_) { +// V v = n.getValidValue(); +// if (v != null && value.equals(v)) +// return true; +// } +// return false; +// } +// +// public void clear() { +// for (ConcurrentSkipListMap.Node n = loNode(); +// isBeforeEnd(n); +// n = n.next_) { +// if (n.getValidValue() != null) +// m.remove(n.key); +// } +// } +// +// /* ---------------- ConcurrentMap API methods -------------- */ +// +// public V putIfAbsent(K key, V value) { +// checkKeyBounds(key); +// return m.putIfAbsent(key, value); +// } +// +// public boolean remove(Object key, Object value) { +// K k = (K)key; +// return inBounds(k) && m.remove(k, value); +// } +// +// public boolean replace(K key, V oldValue, V newValue) { +// checkKeyBounds(key); +// return m.replace(key, oldValue, newValue); +// } +// +// public V replace(K key, V value) { +// checkKeyBounds(key); +// return m.replace(key, value); +// } +// +// /* ---------------- SortedMap API methods -------------- */ +// +// public Comparator comparator() { +// Comparator cmp = m.comparator(); +// if (isDescending) +// return Collections.reverseOrder(cmp); +// else +// return cmp; +// } +// +// /** +// * Utility to create submaps, where given bounds override +// * unbounded(null) ones and/or are checked against bounded ones. +// */ +// private SubMap newSubMap(K fromKey, +// boolean fromInclusive, +// K toKey, +// boolean toInclusive) { +// if (isDescending) { // flip senses +// K tk = fromKey; +// fromKey = toKey; +// toKey = tk; +// boolean ti = fromInclusive; +// fromInclusive = toInclusive; +// toInclusive = ti; +// } +// if (lo != null) { +// if (fromKey == null) { +// fromKey = lo; +// fromInclusive = loInclusive; +// } +// else { +// int c = m.compare(fromKey, lo); +// if (c < 0 || (c == 0 && !loInclusive && fromInclusive)) +// throw new IllegalArgumentException("key out of range"); +// } +// } +// if (hi != null) { +// if (toKey == null) { +// toKey = hi; +// toInclusive = hiInclusive; +// } +// else { +// int c = m.compare(toKey, hi); +// if (c > 0 || (c == 0 && !hiInclusive && toInclusive)) +// throw new IllegalArgumentException("key out of range"); +// } +// } +// return new SubMap(m, fromKey, fromInclusive, +// toKey, toInclusive, isDescending); +// } +// +// public SubMap subMap(K fromKey, +// boolean fromInclusive, +// K toKey, +// boolean toInclusive) { +// if (fromKey == null || toKey == null) +// throw new NullPointerException(); +// return newSubMap(fromKey, fromInclusive, toKey, toInclusive); +// } +// +// public SubMap headMap(K toKey, +// boolean inclusive) { +// if (toKey == null) +// throw new NullPointerException(); +// return newSubMap(null, false, toKey, inclusive); +// } +// +// public SubMap tailMap(K fromKey, +// boolean inclusive) { +// if (fromKey == null) +// throw new NullPointerException(); +// return newSubMap(fromKey, inclusive, null, false); +// } +// +// public SubMap subMap(K fromKey, K toKey) { +// return subMap(fromKey, true, toKey, false); +// } +// +// public SubMap headMap(K toKey) { +// return headMap(toKey, false); +// } +// +// public SubMap tailMap(K fromKey) { +// return tailMap(fromKey, true); +// } +// +// public SubMap descendingMap() { +// return new SubMap(m, lo, loInclusive, +// hi, hiInclusive, !isDescending); +// } +// +// /* ---------------- Relational methods -------------- */ +// +// public Map.Entry ceilingEntry(K key) { +// return getNearEntry(key, (m.GT|m.EQ)); +// } +// +// public K ceilingKey(K key) { +// return getNearKey(key, (m.GT|m.EQ)); +// } +// +// public Map.Entry lowerEntry(K key) { +// return getNearEntry(key, (m.LT)); +// } +// +// public K lowerKey(K key) { +// return getNearKey(key, (m.LT)); +// } +// +// public Map.Entry floorEntry(K key) { +// return getNearEntry(key, (m.LT|m.EQ)); +// } +// +// public K floorKey(K key) { +// return getNearKey(key, (m.LT|m.EQ)); +// } +// +// public Map.Entry higherEntry(K key) { +// return getNearEntry(key, (m.GT)); +// } +// +// public K higherKey(K key) { +// return getNearKey(key, (m.GT)); +// } +// +// public K firstKey() { +// return isDescending? highestKey() : lowestKey(); +// } +// +// public K lastKey() { +// return isDescending? lowestKey() : highestKey(); +// } +// +// public Map.Entry firstEntry() { +// return isDescending? highestEntry() : lowestEntry(); +// } +// +// public Map.Entry lastEntry() { +// return isDescending? lowestEntry() : highestEntry(); +// } +// +// public Map.Entry pollFirstEntry() { +// return isDescending? removeHighest() : removeLowest(); +// } +// +// public Map.Entry pollLastEntry() { +// return isDescending? removeLowest() : removeHighest(); +// } +// +// /* ---------------- Submap Views -------------- */ +// +// public NavigableSet keySet() { +// KeySet ks = keySetView; +// return (ks != null) ? ks : (keySetView = new KeySet(this)); +// } +// +// public NavigableSet navigableKeySet() { +// KeySet ks = keySetView; +// return (ks != null) ? ks : (keySetView = new KeySet(this)); +// } +// +// public Collection values() { +// Collection vs = valuesView; +// return (vs != null) ? vs : (valuesView = new Values(this)); +// } +// +// public Set> entrySet() { +// Set> es = entrySetView; +// return (es != null) ? es : (entrySetView = new EntrySet(this)); +// } +// +// public NavigableSet descendingKeySet() { +// return descendingMap().navigableKeySet(); +// } +// +// Iterator keyIterator() { +// return new SubMapKeyIterator(); +// } +// +// Iterator valueIterator() { +// return new SubMapValueIterator(); +// } +// +// Iterator> entryIterator() { +// return new SubMapEntryIterator(); +// } +// +// /** +// * Variant of main Iter class to traverse through submaps. +// */ +// abstract class SubMapIter implements Iterator { +// /** the last node returned by next() */ +// Node lastReturned; +// /** the next node to return from next(); */ +// Node next; +// /** Cache of next value field to maintain weak consistency */ +// V nextValue; +// +// SubMapIter() { +// for (;;) { +// next = isDescending ? hiNode() : loNode(); +// if (next == null) +// break; +// Object x = next.value; +// if (x != null && x != next) { +// if (! inBounds(next.key)) +// next = null; +// else +// nextValue = (V) x; +// break; +// } +// } +// } +// +// public final boolean hasNext() { +// return next != null; +// } +// +// final void advance() { +// if (next == null) +// throw new NoSuchElementException(); +// lastReturned = next; +// if (isDescending) +// descend(); +// else +// ascend(); +// } +// +// private void ascend() { +// for (;;) { +// next = next.next_; +// if (next == null) +// break; +// Object x = next.value; +// if (x != null && x != next) { +// if (tooHigh(next.key)) +// next = null; +// else +// nextValue = (V) x; +// break; +// } +// } +// } +// +// private void descend() { +// for (;;) { +// next = m.findNear(lastReturned.key, LT); +// if (next == null) +// break; +// Object x = next.value; +// if (x != null && x != next) { +// if (tooLow(next.key)) +// next = null; +// else +// nextValue = (V) x; +// break; +// } +// } +// } +// +// public void remove() { +// Node l = lastReturned; +// if (l == null) +// throw new IllegalStateException(); +// m.remove(l.key); +// lastReturned = null; +// } +// +// } +// +// final class SubMapValueIterator extends SubMapIter { +// public V next() { +// V v = nextValue; +// advance(); +// return v; +// } +// } +// +// final class SubMapKeyIterator extends SubMapIter { +// public K next() { +// Node n = next; +// advance(); +// return n.key; +// } +// } +// +// final class SubMapEntryIterator extends SubMapIter> { +// public Map.Entry next() { +// Node n = next; +// V v = nextValue; +// advance(); +// return new AbstractMap.SimpleImmutableEntry(n.key, v); +// } +// } +// } } diff --git a/sources/net.sf.j2s.java.core/src/java/util/concurrent/ConcurrentSkipListSet.java b/sources/net.sf.j2s.java.core/src/java/util/concurrent/ConcurrentSkipListSet.java index 717dadb7a..735d89a4f 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/concurrent/ConcurrentSkipListSet.java +++ b/sources/net.sf.j2s.java.core/src/java/util/concurrent/ConcurrentSkipListSet.java @@ -36,69 +36,71 @@ package java.util.concurrent; import java.util.*; import sun.misc.Unsafe; - -/** - * A scalable concurrent {@link NavigableSet} implementation based on - * a {@link ConcurrentSkipListMap}. The elements of the set are kept - * sorted according to their {@linkplain Comparable natural ordering}, - * or by a {@link Comparator} provided at set creation time, depending - * on which constructor is used. - * - *

This implementation provides expected average log(n) time - * cost for the contains, add, and remove - * operations and their variants. Insertion, removal, and access - * operations safely execute concurrently by multiple threads. - * Iterators are weakly consistent, returning elements - * reflecting the state of the set at some point at or since the - * creation of the iterator. They do not throw {@link - * ConcurrentModificationException}, and may proceed concurrently with - * other operations. Ascending ordered views and their iterators are - * faster than descending ones. - * - *

Beware that, unlike in most collections, the size - * method is not a constant-time operation. Because of the - * asynchronous nature of these sets, determining the current number - * of elements requires a traversal of the elements. Additionally, the - * bulk operations addAll, removeAll, - * retainAll, and containsAll are not - * guaranteed to be performed atomically. For example, an iterator - * operating concurrently with an addAll operation might view - * only some of the added elements. - * - *

This class and its iterators implement all of the - * optional methods of the {@link Set} and {@link Iterator} - * interfaces. Like most other concurrent collection implementations, - * this class does not permit the use of null elements, - * because null arguments and return values cannot be reliably - * distinguished from the absence of elements. - * - *

This class is a member of the - * - * Java Collections Framework. - * - * @author Doug Lea - * @param the type of elements maintained by this set - * @since 1.6 - */ -public class ConcurrentSkipListSet - extends AbstractSet - implements NavigableSet, Cloneable, java.io.Serializable { - - private static final long serialVersionUID = -2479143111061671589L; - - /** - * The underlying map. Uses Boolean.TRUE as value for each - * element. This field is declared final for the sake of thread - * safety, which entails some ugliness in clone() - */ - private final ConcurrentNavigableMap m; - +import swingjs.JSUtil; + +///** +// * A scalable concurrent {@link NavigableSet} implementation based on +// * a {@link ConcurrentSkipListMap}. The elements of the set are kept +// * sorted according to their {@linkplain Comparable natural ordering}, +// * or by a {@link Comparator} provided at set creation time, depending +// * on which constructor is used. +// * +// *

This implementation provides expected average log(n) time +// * cost for the contains, add, and remove +// * operations and their variants. Insertion, removal, and access +// * operations safely execute concurrently by multiple threads. +// * Iterators are weakly consistent, returning elements +// * reflecting the state of the set at some point at or since the +// * creation of the iterator. They do not throw {@link +// * ConcurrentModificationException}, and may proceed concurrently with +// * other operations. Ascending ordered views and their iterators are +// * faster than descending ones. +// * +// *

Beware that, unlike in most collections, the size +// * method is not a constant-time operation. Because of the +// * asynchronous nature of these sets, determining the current number +// * of elements requires a traversal of the elements. Additionally, the +// * bulk operations addAll, removeAll, +// * retainAll, and containsAll are not +// * guaranteed to be performed atomically. For example, an iterator +// * operating concurrently with an addAll operation might view +// * only some of the added elements. +// * +// *

This class and its iterators implement all of the +// * optional methods of the {@link Set} and {@link Iterator} +// * interfaces. Like most other concurrent collection implementations, +// * this class does not permit the use of null elements, +// * because null arguments and return values cannot be reliably +// * distinguished from the absence of elements. +// * +// *

This class is a member of the +// * +// * Java Collections Framework. +// * +// * @author Doug Lea +// * @param the type of elements maintained by this set +// * @since 1.6 +// */ +public class ConcurrentSkipListSet extends HashSet { +// extends AbstractSet +// implements NavigableSet, Cloneable, java.io.Serializable { +// +// private static final long serialVersionUID = -2479143111061671589L; +// +// /** +// * The underlying map. Uses Boolean.TRUE as value for each +// * element. This field is declared final for the sake of thread +// * safety, which entails some ugliness in clone() +// */ +// private final ConcurrentNavigableMap m; +// /** * Constructs a new, empty set that orders its elements according to * their {@linkplain Comparable natural ordering}. */ public ConcurrentSkipListSet() { - m = new ConcurrentSkipListMap(); + super(); +// m = new ConcurrentSkipListMap(); } /** @@ -110,7 +112,8 @@ public ConcurrentSkipListSet() { * ordering} of the elements will be used. */ public ConcurrentSkipListSet(Comparator comparator) { - m = new ConcurrentSkipListMap(comparator); +// super(comparator); +// m = new ConcurrentSkipListMap(comparator); } /** @@ -125,8 +128,9 @@ public ConcurrentSkipListSet(Comparator comparator) { * of its elements are null */ public ConcurrentSkipListSet(Collection c) { - m = new ConcurrentSkipListMap(); - addAll(c); + super(c); +// m = new ConcurrentSkipListMap(); +// addAll(c); } /** @@ -138,7 +142,8 @@ public ConcurrentSkipListSet(Collection c) { * of its elements are null */ public ConcurrentSkipListSet(SortedSet s) { - m = new ConcurrentSkipListMap(s.comparator()); + super(); +// m = new ConcurrentSkipListMap(s.comparator()); addAll(s); } @@ -146,340 +151,342 @@ public ConcurrentSkipListSet(SortedSet s) { * For use by submaps */ ConcurrentSkipListSet(ConcurrentNavigableMap m) { - this.m = m; - } - - /** - * Returns a shallow copy of this ConcurrentSkipListSet - * instance. (The elements themselves are not cloned.) - * - * @return a shallow copy of this set - */ - public ConcurrentSkipListSet clone() { - ConcurrentSkipListSet clone = null; - try { - clone = (ConcurrentSkipListSet) super.clone(); - clone.setMap(new ConcurrentSkipListMap(m)); - } catch (CloneNotSupportedException e) { - throw new InternalError(); - } - - return clone; - } - - /* ---------------- Set operations -------------- */ - - /** - * Returns the number of elements in this set. If this set - * contains more than Integer.MAX_VALUE elements, it - * returns Integer.MAX_VALUE. - * - *

Beware that, unlike in most collections, this method is - * NOT a constant-time operation. Because of the - * asynchronous nature of these sets, determining the current - * number of elements requires traversing them all to count them. - * Additionally, it is possible for the size to change during - * execution of this method, in which case the returned result - * will be inaccurate. Thus, this method is typically not very - * useful in concurrent applications. - * - * @return the number of elements in this set - */ - public int size() { - return m.size(); - } - - /** - * Returns true if this set contains no elements. - * @return true if this set contains no elements - */ - public boolean isEmpty() { - return m.isEmpty(); - } - - /** - * Returns true if this set contains the specified element. - * More formally, returns true if and only if this set - * contains an element e such that o.equals(e). - * - * @param o object to be checked for containment in this set - * @return true if this set contains the specified element - * @throws ClassCastException if the specified element cannot be - * compared with the elements currently in this set - * @throws NullPointerException if the specified element is null - */ - public boolean contains(Object o) { - return m.containsKey(o); - } - - /** - * Adds the specified element to this set if it is not already present. - * More formally, adds the specified element e to this set if - * the set contains no element e2 such that e.equals(e2). - * If this set already contains the element, the call leaves the set - * unchanged and returns false. - * - * @param e element to be added to this set - * @return true if this set did not already contain the - * specified element - * @throws ClassCastException if e cannot be compared - * with the elements currently in this set - * @throws NullPointerException if the specified element is null - */ - public boolean add(E e) { - return m.putIfAbsent(e, Boolean.TRUE) == null; - } - - /** - * Removes the specified element from this set if it is present. - * More formally, removes an element e such that - * o.equals(e), if this set contains such an element. - * Returns true if this set contained the element (or - * equivalently, if this set changed as a result of the call). - * (This set will not contain the element once the call returns.) - * - * @param o object to be removed from this set, if present - * @return true if this set contained the specified element - * @throws ClassCastException if o cannot be compared - * with the elements currently in this set - * @throws NullPointerException if the specified element is null - */ - public boolean remove(Object o) { - return m.remove(o, Boolean.TRUE); - } - - /** - * Removes all of the elements from this set. - */ - public void clear() { - m.clear(); - } - - /** - * Returns an iterator over the elements in this set in ascending order. - * - * @return an iterator over the elements in this set in ascending order - */ - public Iterator iterator() { - return m.navigableKeySet().iterator(); - } - - /** - * Returns an iterator over the elements in this set in descending order. - * - * @return an iterator over the elements in this set in descending order - */ - public Iterator descendingIterator() { - return m.descendingKeySet().iterator(); - } - - - /* ---------------- AbstractSet Overrides -------------- */ - - /** - * Compares the specified object with this set for equality. Returns - * true if the specified object is also a set, the two sets - * have the same size, and every member of the specified set is - * contained in this set (or equivalently, every member of this set is - * contained in the specified set). This definition ensures that the - * equals method works properly across different implementations of the - * set interface. - * - * @param o the object to be compared for equality with this set - * @return true if the specified object is equal to this set - */ - public boolean equals(Object o) { - // Override AbstractSet version to avoid calling size() - if (o == this) - return true; - if (!(o instanceof Set)) - return false; - Collection c = (Collection) o; - try { - return containsAll(c) && c.containsAll(this); - } catch (ClassCastException unused) { - return false; - } catch (NullPointerException unused) { - return false; - } - } - - /** - * Removes from this set all of its elements that are contained in - * the specified collection. If the specified collection is also - * a set, this operation effectively modifies this set so that its - * value is the asymmetric set difference of the two sets. - * - * @param c collection containing elements to be removed from this set - * @return true if this set changed as a result of the call - * @throws ClassCastException if the types of one or more elements in this - * set are incompatible with the specified collection - * @throws NullPointerException if the specified collection or any - * of its elements are null - */ - public boolean removeAll(Collection c) { - // Override AbstractSet version to avoid unnecessary call to size() - boolean modified = false; - for (Iterator i = c.iterator(); i.hasNext(); ) - if (remove(i.next())) - modified = true; - return modified; - } - - /* ---------------- Relational operations -------------- */ - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified element is null - */ - public E lower(E e) { - return m.lowerKey(e); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified element is null - */ - public E floor(E e) { - return m.floorKey(e); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified element is null - */ - public E ceiling(E e) { - return m.ceilingKey(e); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if the specified element is null - */ - public E higher(E e) { - return m.higherKey(e); - } - - public E pollFirst() { - Map.Entry e = m.pollFirstEntry(); - return e == null? null : e.getKey(); - } - - public E pollLast() { - Map.Entry e = m.pollLastEntry(); - return e == null? null : e.getKey(); - } - - - /* ---------------- SortedSet operations -------------- */ - - - public Comparator comparator() { - return m.comparator(); - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E first() { - return m.firstKey(); - } - - /** - * @throws NoSuchElementException {@inheritDoc} - */ - public E last() { - return m.lastKey(); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code fromElement} or - * {@code toElement} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public NavigableSet subSet(E fromElement, - boolean fromInclusive, - E toElement, - boolean toInclusive) { - return new ConcurrentSkipListSet - (m.subMap(fromElement, fromInclusive, - toElement, toInclusive)); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code toElement} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public NavigableSet headSet(E toElement, boolean inclusive) { - return new ConcurrentSkipListSet(m.headMap(toElement, inclusive)); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code fromElement} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public NavigableSet tailSet(E fromElement, boolean inclusive) { - return new ConcurrentSkipListSet(m.tailMap(fromElement, inclusive)); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code fromElement} or - * {@code toElement} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public NavigableSet subSet(E fromElement, E toElement) { - return subSet(fromElement, true, toElement, false); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code toElement} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public NavigableSet headSet(E toElement) { - return headSet(toElement, false); - } - - /** - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException if {@code fromElement} is null - * @throws IllegalArgumentException {@inheritDoc} - */ - public NavigableSet tailSet(E fromElement) { - return tailSet(fromElement, true); - } - - /** - * Returns a reverse order view of the elements contained in this set. - * The descending set is backed by this set, so changes to the set are - * reflected in the descending set, and vice-versa. - * - *

The returned set has an ordering equivalent to - * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator()). - * The expression {@code s.descendingSet().descendingSet()} returns a - * view of {@code s} essentially equivalent to {@code s}. - * - * @return a reverse order view of this set - */ - public NavigableSet descendingSet() { - return new ConcurrentSkipListSet(m.descendingMap()); - } - - // Support for resetting map in clone -// private static final Unsafe unsafe = Unsafe.getUnsafe(); -// private static final long mapOffset; -// static { + // hmm!!! + JSUtil.notImplemented(null); +// this.m = m; + } +// +// /** +// * Returns a shallow copy of this ConcurrentSkipListSet +// * instance. (The elements themselves are not cloned.) +// * +// * @return a shallow copy of this set +// */ +// public ConcurrentSkipListSet clone() { +// ConcurrentSkipListSet clone = null; // try { -// mapOffset = unsafe.objectFieldOffset -// (ConcurrentSkipListSet.class.getDeclaredField("m")); -// } catch (Exception ex) { throw new Error(ex); } +// clone = (ConcurrentSkipListSet) super.clone(); +// clone.setMap(new ConcurrentSkipListMap(m)); +// } catch (CloneNotSupportedException e) { +// throw new InternalError(); +// } +// +// return clone; // } - private void setMap(ConcurrentNavigableMap map) { - //unsafe.putObjectVolatile(this, mapOffset, map); - } - +// +// /* ---------------- Set operations -------------- */ +// +// /** +// * Returns the number of elements in this set. If this set +// * contains more than Integer.MAX_VALUE elements, it +// * returns Integer.MAX_VALUE. +// * +// *

Beware that, unlike in most collections, this method is +// * NOT a constant-time operation. Because of the +// * asynchronous nature of these sets, determining the current +// * number of elements requires traversing them all to count them. +// * Additionally, it is possible for the size to change during +// * execution of this method, in which case the returned result +// * will be inaccurate. Thus, this method is typically not very +// * useful in concurrent applications. +// * +// * @return the number of elements in this set +// */ +// public int size() { +// return m.size(); +// } +// +// /** +// * Returns true if this set contains no elements. +// * @return true if this set contains no elements +// */ +// public boolean isEmpty() { +// return m.isEmpty(); +// } +// +// /** +// * Returns true if this set contains the specified element. +// * More formally, returns true if and only if this set +// * contains an element e such that o.equals(e). +// * +// * @param o object to be checked for containment in this set +// * @return true if this set contains the specified element +// * @throws ClassCastException if the specified element cannot be +// * compared with the elements currently in this set +// * @throws NullPointerException if the specified element is null +// */ +// public boolean contains(Object o) { +// return m.containsKey(o); +// } +// +// /** +// * Adds the specified element to this set if it is not already present. +// * More formally, adds the specified element e to this set if +// * the set contains no element e2 such that e.equals(e2). +// * If this set already contains the element, the call leaves the set +// * unchanged and returns false. +// * +// * @param e element to be added to this set +// * @return true if this set did not already contain the +// * specified element +// * @throws ClassCastException if e cannot be compared +// * with the elements currently in this set +// * @throws NullPointerException if the specified element is null +// */ +// public boolean add(E e) { +// return m.putIfAbsent(e, Boolean.TRUE) == null; +// } +// +// /** +// * Removes the specified element from this set if it is present. +// * More formally, removes an element e such that +// * o.equals(e), if this set contains such an element. +// * Returns true if this set contained the element (or +// * equivalently, if this set changed as a result of the call). +// * (This set will not contain the element once the call returns.) +// * +// * @param o object to be removed from this set, if present +// * @return true if this set contained the specified element +// * @throws ClassCastException if o cannot be compared +// * with the elements currently in this set +// * @throws NullPointerException if the specified element is null +// */ +// public boolean remove(Object o) { +// return m.remove(o, Boolean.TRUE); +// } +// +// /** +// * Removes all of the elements from this set. +// */ +// public void clear() { +// m.clear(); +// } +// +// /** +// * Returns an iterator over the elements in this set in ascending order. +// * +// * @return an iterator over the elements in this set in ascending order +// */ +// public Iterator iterator() { +// return m.navigableKeySet().iterator(); +// } +// +// /** +// * Returns an iterator over the elements in this set in descending order. +// * +// * @return an iterator over the elements in this set in descending order +// */ +// public Iterator descendingIterator() { +// return m.descendingKeySet().iterator(); +// } +// +// +// /* ---------------- AbstractSet Overrides -------------- */ +// +// /** +// * Compares the specified object with this set for equality. Returns +// * true if the specified object is also a set, the two sets +// * have the same size, and every member of the specified set is +// * contained in this set (or equivalently, every member of this set is +// * contained in the specified set). This definition ensures that the +// * equals method works properly across different implementations of the +// * set interface. +// * +// * @param o the object to be compared for equality with this set +// * @return true if the specified object is equal to this set +// */ +// public boolean equals(Object o) { +// // Override AbstractSet version to avoid calling size() +// if (o == this) +// return true; +// if (!(o instanceof Set)) +// return false; +// Collection c = (Collection) o; +// try { +// return containsAll(c) && c.containsAll(this); +// } catch (ClassCastException unused) { +// return false; +// } catch (NullPointerException unused) { +// return false; +// } +// } +// +// /** +// * Removes from this set all of its elements that are contained in +// * the specified collection. If the specified collection is also +// * a set, this operation effectively modifies this set so that its +// * value is the asymmetric set difference of the two sets. +// * +// * @param c collection containing elements to be removed from this set +// * @return true if this set changed as a result of the call +// * @throws ClassCastException if the types of one or more elements in this +// * set are incompatible with the specified collection +// * @throws NullPointerException if the specified collection or any +// * of its elements are null +// */ +// public boolean removeAll(Collection c) { +// // Override AbstractSet version to avoid unnecessary call to size() +// boolean modified = false; +// for (Iterator i = c.iterator(); i.hasNext(); ) +// if (remove(i.next())) +// modified = true; +// return modified; +// } +// +// /* ---------------- Relational operations -------------- */ +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified element is null +// */ +// public E lower(E e) { +// return m.lowerKey(e); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified element is null +// */ +// public E floor(E e) { +// return m.floorKey(e); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified element is null +// */ +// public E ceiling(E e) { +// return m.ceilingKey(e); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if the specified element is null +// */ +// public E higher(E e) { +// return m.higherKey(e); +// } +// +// public E pollFirst() { +// Map.Entry e = m.pollFirstEntry(); +// return e == null? null : e.getKey(); +// } +// +// public E pollLast() { +// Map.Entry e = m.pollLastEntry(); +// return e == null? null : e.getKey(); +// } +// +// +// /* ---------------- SortedSet operations -------------- */ +// +// +// public Comparator comparator() { +// return m.comparator(); +// } +// +// /** +// * @throws NoSuchElementException {@inheritDoc} +// */ +// public E first() { +// return m.firstKey(); +// } +// +// /** +// * @throws NoSuchElementException {@inheritDoc} +// */ +// public E last() { +// return m.lastKey(); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code fromElement} or +// * {@code toElement} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public NavigableSet subSet(E fromElement, +// boolean fromInclusive, +// E toElement, +// boolean toInclusive) { +// return new ConcurrentSkipListSet +// (m.subMap(fromElement, fromInclusive, +// toElement, toInclusive)); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code toElement} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public NavigableSet headSet(E toElement, boolean inclusive) { +// return new ConcurrentSkipListSet(m.headMap(toElement, inclusive)); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code fromElement} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public NavigableSet tailSet(E fromElement, boolean inclusive) { +// return new ConcurrentSkipListSet(m.tailMap(fromElement, inclusive)); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code fromElement} or +// * {@code toElement} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public NavigableSet subSet(E fromElement, E toElement) { +// return subSet(fromElement, true, toElement, false); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code toElement} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public NavigableSet headSet(E toElement) { +// return headSet(toElement, false); +// } +// +// /** +// * @throws ClassCastException {@inheritDoc} +// * @throws NullPointerException if {@code fromElement} is null +// * @throws IllegalArgumentException {@inheritDoc} +// */ +// public NavigableSet tailSet(E fromElement) { +// return tailSet(fromElement, true); +// } +// +// /** +// * Returns a reverse order view of the elements contained in this set. +// * The descending set is backed by this set, so changes to the set are +// * reflected in the descending set, and vice-versa. +// * +// *

The returned set has an ordering equivalent to +// * {@link Collections#reverseOrder(Comparator) Collections.reverseOrder}(comparator()). +// * The expression {@code s.descendingSet().descendingSet()} returns a +// * view of {@code s} essentially equivalent to {@code s}. +// * +// * @return a reverse order view of this set +// */ +// public NavigableSet descendingSet() { +// return new ConcurrentSkipListSet(m.descendingMap()); +// } +// +// // Support for resetting map in clone +//// private static final Unsafe unsafe = Unsafe.getUnsafe(); +//// private static final long mapOffset; +//// static { +//// try { +//// mapOffset = unsafe.objectFieldOffset +//// (ConcurrentSkipListSet.class.getDeclaredField("m")); +//// } catch (Exception ex) { throw new Error(ex); } +//// } +// private void setMap(ConcurrentNavigableMap map) { +// //unsafe.putObjectVolatile(this, mapOffset, map); +// } +// } diff --git a/sources/net.sf.j2s.java.core/src/java/util/concurrent/CopyOnWriteArrayList.java b/sources/net.sf.j2s.java.core/src/java/util/concurrent/CopyOnWriteArrayList.java index 20bd4871b..c78015b64 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/concurrent/CopyOnWriteArrayList.java +++ b/sources/net.sf.j2s.java.core/src/java/util/concurrent/CopyOnWriteArrayList.java @@ -38,73 +38,75 @@ import java.util.concurrent.locks.*; import sun.misc.Unsafe; -/** - * A thread-safe variant of {@link java.util.ArrayList} in which all mutative - * operations (add, set, and so on) are implemented by - * making a fresh copy of the underlying array. - * - *

This is ordinarily too costly, but may be more efficient - * than alternatives when traversal operations vastly outnumber - * mutations, and is useful when you cannot or don't want to - * synchronize traversals, yet need to preclude interference among - * concurrent threads. The "snapshot" style iterator method uses a - * reference to the state of the array at the point that the iterator - * was created. This array never changes during the lifetime of the - * iterator, so interference is impossible and the iterator is - * guaranteed not to throw ConcurrentModificationException. - * The iterator will not reflect additions, removals, or changes to - * the list since the iterator was created. Element-changing - * operations on iterators themselves (remove, set, and - * add) are not supported. These methods throw - * UnsupportedOperationException. - * - *

All elements are permitted, including null. - * - *

Memory consistency effects: As with other concurrent - * collections, actions in a thread prior to placing an object into a - * {@code CopyOnWriteArrayList} - * happen-before - * actions subsequent to the access or removal of that element from - * the {@code CopyOnWriteArrayList} in another thread. - * - *

This class is a member of the - * - * Java Collections Framework. - * - * @since 1.5 - * @author Doug Lea - * @param the type of elements held in this collection - */ -public class CopyOnWriteArrayList - implements List, RandomAccess, Cloneable, java.io.Serializable { - private static final long serialVersionUID = 8673264195747942595L; - - /** The lock protecting all mutators */ - transient final ReentrantLock lock = new ReentrantLock(); - - /** The array, accessed only via getArray/setArray. */ - private volatile transient Object[] array; - - /** - * Gets the array. Non-private so as to also be accessible - * from CopyOnWriteArraySet class. - */ - final Object[] getArray() { - return array; - } - - /** - * Sets the array. - */ - final void setArray(Object[] a) { - array = a; - } +///** +// * A thread-safe variant of {@link java.util.ArrayList} in which all mutative +// * operations (add, set, and so on) are implemented by +// * making a fresh copy of the underlying array. +// * +// *

This is ordinarily too costly, but may be more efficient +// * than alternatives when traversal operations vastly outnumber +// * mutations, and is useful when you cannot or don't want to +// * synchronize traversals, yet need to preclude interference among +// * concurrent threads. The "snapshot" style iterator method uses a +// * reference to the state of the array at the point that the iterator +// * was created. This array never changes during the lifetime of the +// * iterator, so interference is impossible and the iterator is +// * guaranteed not to throw ConcurrentModificationException. +// * The iterator will not reflect additions, removals, or changes to +// * the list since the iterator was created. Element-changing +// * operations on iterators themselves (remove, set, and +// * add) are not supported. These methods throw +// * UnsupportedOperationException. +// * +// *

All elements are permitted, including null. +// * +// *

Memory consistency effects: As with other concurrent +// * collections, actions in a thread prior to placing an object into a +// * {@code CopyOnWriteArrayList} +// * happen-before +// * actions subsequent to the access or removal of that element from +// * the {@code CopyOnWriteArrayList} in another thread. +// * +// *

This class is a member of the +// * +// * Java Collections Framework. +// * +// * @since 1.5 +// * @author Doug Lea +// * @param the type of elements held in this collection +// */ +public class CopyOnWriteArrayList extends ArrayList +// implements List, RandomAccess, Cloneable, java.io.Serializable + { +// private static final long serialVersionUID = 8673264195747942595L; +// +// /** The lock protecting all mutators */ +// transient final ReentrantLock lock = new ReentrantLock(); +// +// /** The array, accessed only via getArray/setArray. */ +// private volatile transient Object[] array; +// +// /** +// * Gets the array. Non-private so as to also be accessible +// * from CopyOnWriteArraySet class. +// */ +// final Object[] getArray() { +// return array; +// } +// +// /** +// * Sets the array. +// */ +// final void setArray(Object[] a) { +// array = a; +// } /** * Creates an empty list. */ public CopyOnWriteArrayList() { - setArray(new Object[0]); + super(); +// setArray(new Object[0]); } /** @@ -116,11 +118,12 @@ public CopyOnWriteArrayList() { * @throws NullPointerException if the specified collection is null */ public CopyOnWriteArrayList(Collection c) { - Object[] elements = c.toArray(); - // c.toArray might (incorrectly) not return Object[] (see 6260652) - if (elements.getClass() != Object[].class) - elements = Arrays.copyOf(elements, elements.length, Object[].class); - setArray(elements); + super(c); +// Object[] elements = c.toArray(); +// // c.toArray might (incorrectly) not return Object[] (see 6260652) +// if (elements.getClass() != Object[].class) +// elements = Arrays.copyOf(elements, elements.length, Object[].class); +// setArray(elements); } /** @@ -131,1210 +134,1213 @@ public CopyOnWriteArrayList(Collection c) { * @throws NullPointerException if the specified array is null */ public CopyOnWriteArrayList(E[] toCopyIn) { - setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); - } - - /** - * Returns the number of elements in this list. - * - * @return the number of elements in this list - */ - public int size() { - return getArray().length; - } - - /** - * Returns true if this list contains no elements. - * - * @return true if this list contains no elements - */ - public boolean isEmpty() { - return size() == 0; - } - - /** - * Test for equality, coping with nulls. - */ - private static boolean eq(Object o1, Object o2) { - return (o1 == null ? o2 == null : o1.equals(o2)); - } - - /** - * static version of indexOf, to allow repeated calls without - * needing to re-acquire array each time. - * @param o element to search for - * @param elements the array - * @param index first index to search - * @param fence one past last index to search - * @return index of element, or -1 if absent - */ - private static int indexOf(Object o, Object[] elements, - int index, int fence) { - if (o == null) { - for (int i = index; i < fence; i++) - if (elements[i] == null) - return i; - } else { - for (int i = index; i < fence; i++) - if (o.equals(elements[i])) - return i; - } - return -1; - } - - /** - * static version of lastIndexOf. - * @param o element to search for - * @param elements the array - * @param index first index to search - * @return index of element, or -1 if absent - */ - private static int lastIndexOf(Object o, Object[] elements, int index) { - if (o == null) { - for (int i = index; i >= 0; i--) - if (elements[i] == null) - return i; - } else { - for (int i = index; i >= 0; i--) - if (o.equals(elements[i])) - return i; - } - return -1; - } - - /** - * Returns true if this list contains the specified element. - * More formally, returns true if and only if this list contains - * at least one element e such that - * (o==null ? e==null : o.equals(e)). - * - * @param o element whose presence in this list is to be tested - * @return true if this list contains the specified element - */ - public boolean contains(Object o) { - Object[] elements = getArray(); - return indexOf(o, elements, 0, elements.length) >= 0; - } - - /** - * {@inheritDoc} - */ - public int indexOf(Object o) { - Object[] elements = getArray(); - return indexOf(o, elements, 0, elements.length); - } - - /** - * Returns the index of the first occurrence of the specified element in - * this list, searching forwards from index, or returns -1 if - * the element is not found. - * More formally, returns the lowest index i such that - * (i >= index && (e==null ? get(i)==null : e.equals(get(i)))), - * or -1 if there is no such index. - * - * @param e element to search for - * @param index index to start searching from - * @return the index of the first occurrence of the element in - * this list at position index or later in the list; - * -1 if the element is not found. - * @throws IndexOutOfBoundsException if the specified index is negative - */ - public int indexOf(E e, int index) { - Object[] elements = getArray(); - return indexOf(e, elements, index, elements.length); - } - - /** - * {@inheritDoc} - */ - public int lastIndexOf(Object o) { - Object[] elements = getArray(); - return lastIndexOf(o, elements, elements.length - 1); - } - - /** - * Returns the index of the last occurrence of the specified element in - * this list, searching backwards from index, or returns -1 if - * the element is not found. - * More formally, returns the highest index i such that - * (i <= index && (e==null ? get(i)==null : e.equals(get(i)))), - * or -1 if there is no such index. - * - * @param e element to search for - * @param index index to start searching backwards from - * @return the index of the last occurrence of the element at position - * less than or equal to index in this list; - * -1 if the element is not found. - * @throws IndexOutOfBoundsException if the specified index is greater - * than or equal to the current size of this list - */ - public int lastIndexOf(E e, int index) { - Object[] elements = getArray(); - return lastIndexOf(e, elements, index); - } - - /** - * Returns a shallow copy of this list. (The elements themselves - * are not copied.) - * - * @return a clone of this list - */ - public Object clone() { - try { - CopyOnWriteArrayList c = (CopyOnWriteArrayList)(super.clone()); -// c.resetLock(); - return c; - } catch (CloneNotSupportedException e) { - // this shouldn't happen, since we are Cloneable - throw new InternalError(); - } - } - - /** - * Returns an array containing all of the elements in this list - * in proper sequence (from first to last element). - * - *

The returned array will be "safe" in that no references to it are - * maintained by this list. (In other words, this method must allocate - * a new array). The caller is thus free to modify the returned array. - * - *

This method acts as bridge between array-based and collection-based - * APIs. - * - * @return an array containing all the elements in this list - */ - public Object[] toArray() { - Object[] elements = getArray(); - return Arrays.copyOf(elements, elements.length); - } - - /** - * Returns an array containing all of the elements in this list in - * proper sequence (from first to last element); the runtime type of - * the returned array is that of the specified array. If the list fits - * in the specified array, it is returned therein. Otherwise, a new - * array is allocated with the runtime type of the specified array and - * the size of this list. - * - *

If this list fits in the specified array with room to spare - * (i.e., the array has more elements than this list), the element in - * the array immediately following the end of the list is set to - * null. (This is useful in determining the length of this - * list only if the caller knows that this list does not contain - * any null elements.) - * - *

Like the {@link #toArray()} method, this method acts as bridge between - * array-based and collection-based APIs. Further, this method allows - * precise control over the runtime type of the output array, and may, - * under certain circumstances, be used to save allocation costs. - * - *

Suppose x is a list known to contain only strings. - * The following code can be used to dump the list into a newly - * allocated array of String: - * - *

-     *     String[] y = x.toArray(new String[0]);
- * - * Note that toArray(new Object[0]) is identical in function to - * toArray(). - * - * @param a the array into which the elements of the list are to - * be stored, if it is big enough; otherwise, a new array of the - * same runtime type is allocated for this purpose. - * @return an array containing all the elements in this list - * @throws ArrayStoreException if the runtime type of the specified array - * is not a supertype of the runtime type of every element in - * this list - * @throws NullPointerException if the specified array is null - */ - @SuppressWarnings("unchecked") - public T[] toArray(T a[]) { - Object[] elements = getArray(); - int len = elements.length; - if (a.length < len) - return (T[]) Arrays.copyOf(elements, len, a.getClass()); - else { - System.arraycopy(elements, 0, a, 0, len); - if (a.length > len) - a[len] = null; - return a; - } - } - - // Positional Access Operations - - @SuppressWarnings("unchecked") - private E get(Object[] a, int index) { - return (E) a[index]; - } - - /** - * {@inheritDoc} - * - * @throws IndexOutOfBoundsException {@inheritDoc} - */ - public E get(int index) { - return get(getArray(), index); - } - - /** - * Replaces the element at the specified position in this list with the - * specified element. - * - * @throws IndexOutOfBoundsException {@inheritDoc} - */ - public E set(int index, E element) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - E oldValue = get(elements, index); - - if (oldValue != element) { - int len = elements.length; - Object[] newElements = Arrays.copyOf(elements, len); - newElements[index] = element; - setArray(newElements); - } else { - // Not quite a no-op; ensures volatile write semantics - setArray(elements); - } - return oldValue; - } finally { - lock.unlock(); - } - } - - /** - * Appends the specified element to the end of this list. - * - * @param e element to be appended to this list - * @return true (as specified by {@link Collection#add}) - */ - public boolean add(E e) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - Object[] newElements = Arrays.copyOf(elements, len + 1); - newElements[len] = e; - setArray(newElements); - return true; - } finally { - lock.unlock(); - } - } - - /** - * Inserts the specified element at the specified position in this - * list. Shifts the element currently at that position (if any) and - * any subsequent elements to the right (adds one to their indices). - * - * @throws IndexOutOfBoundsException {@inheritDoc} - */ - public void add(int index, E element) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - if (index > len || index < 0) - throw new IndexOutOfBoundsException("Index: "+index+ - ", Size: "+len); - Object[] newElements; - int numMoved = len - index; - if (numMoved == 0) - newElements = Arrays.copyOf(elements, len + 1); - else { - newElements = new Object[len + 1]; - System.arraycopy(elements, 0, newElements, 0, index); - System.arraycopy(elements, index, newElements, index + 1, - numMoved); - } - newElements[index] = element; - setArray(newElements); - } finally { - lock.unlock(); - } - } - - /** - * Removes the element at the specified position in this list. - * Shifts any subsequent elements to the left (subtracts one from their - * indices). Returns the element that was removed from the list. - * - * @throws IndexOutOfBoundsException {@inheritDoc} - */ - public E remove(int index) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - E oldValue = get(elements, index); - int numMoved = len - index - 1; - if (numMoved == 0) - setArray(Arrays.copyOf(elements, len - 1)); - else { - Object[] newElements = new Object[len - 1]; - System.arraycopy(elements, 0, newElements, 0, index); - System.arraycopy(elements, index + 1, newElements, index, - numMoved); - setArray(newElements); - } - return oldValue; - } finally { - lock.unlock(); - } - } - - /** - * Removes the first occurrence of the specified element from this list, - * if it is present. If this list does not contain the element, it is - * unchanged. More formally, removes the element with the lowest index - * i such that - * (o==null ? get(i)==null : o.equals(get(i))) - * (if such an element exists). Returns true if this list - * contained the specified element (or equivalently, if this list - * changed as a result of the call). - * - * @param o element to be removed from this list, if present - * @return true if this list contained the specified element - */ - public boolean remove(Object o) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - if (len != 0) { - // Copy while searching for element to remove - // This wins in the normal case of element being present - int newlen = len - 1; - Object[] newElements = new Object[newlen]; - - for (int i = 0; i < newlen; ++i) { - if (eq(o, elements[i])) { - // found one; copy remaining and exit - for (int k = i + 1; k < len; ++k) - newElements[k-1] = elements[k]; - setArray(newElements); - return true; - } else - newElements[i] = elements[i]; - } - - // special handling for last cell - if (eq(o, elements[newlen])) { - setArray(newElements); - return true; - } - } - return false; - } finally { - lock.unlock(); - } - } - - /** - * Removes from this list all of the elements whose index is between - * fromIndex, inclusive, and toIndex, exclusive. - * Shifts any succeeding elements to the left (reduces their index). - * This call shortens the list by (toIndex - fromIndex) elements. - * (If toIndex==fromIndex, this operation has no effect.) - * - * @param fromIndex index of first element to be removed - * @param toIndex index after last element to be removed - * @throws IndexOutOfBoundsException if fromIndex or toIndex out of range - * (@code{fromIndex < 0 || toIndex > size() || toIndex < fromIndex}) - */ - private void removeRange(int fromIndex, int toIndex) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - - if (fromIndex < 0 || toIndex > len || toIndex < fromIndex) - throw new IndexOutOfBoundsException(); - int newlen = len - (toIndex - fromIndex); - int numMoved = len - toIndex; - if (numMoved == 0) - setArray(Arrays.copyOf(elements, newlen)); - else { - Object[] newElements = new Object[newlen]; - System.arraycopy(elements, 0, newElements, 0, fromIndex); - System.arraycopy(elements, toIndex, newElements, - fromIndex, numMoved); - setArray(newElements); - } - } finally { - lock.unlock(); - } - } - - /** - * Append the element if not present. - * - * @param e element to be added to this list, if absent - * @return true if the element was added - */ - public boolean addIfAbsent(E e) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - // Copy while checking if already present. - // This wins in the most common case where it is not present - Object[] elements = getArray(); - int len = elements.length; - Object[] newElements = new Object[len + 1]; - for (int i = 0; i < len; ++i) { - if (eq(e, elements[i])) - return false; // exit, throwing away copy - else - newElements[i] = elements[i]; - } - newElements[len] = e; - setArray(newElements); - return true; - } finally { - lock.unlock(); - } - } - - /** - * Returns true if this list contains all of the elements of the - * specified collection. - * - * @param c collection to be checked for containment in this list - * @return true if this list contains all of the elements of the - * specified collection - * @throws NullPointerException if the specified collection is null - * @see #contains(Object) - */ - public boolean containsAll(Collection c) { - Object[] elements = getArray(); - int len = elements.length; - for (Object e : c) { - if (indexOf(e, elements, 0, len) < 0) - return false; - } - return true; - } - - /** - * Removes from this list all of its elements that are contained in - * the specified collection. This is a particularly expensive operation - * in this class because of the need for an internal temporary array. - * - * @param c collection containing elements to be removed from this list - * @return true if this list changed as a result of the call - * @throws ClassCastException if the class of an element of this list - * is incompatible with the specified collection (optional) - * @throws NullPointerException if this list contains a null element and the - * specified collection does not permit null elements (optional), - * or if the specified collection is null - * @see #remove(Object) - */ - public boolean removeAll(Collection c) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - if (len != 0) { - // temp array holds those elements we know we want to keep - int newlen = 0; - Object[] temp = new Object[len]; - for (int i = 0; i < len; ++i) { - Object element = elements[i]; - if (!c.contains(element)) - temp[newlen++] = element; - } - if (newlen != len) { - setArray(Arrays.copyOf(temp, newlen)); - return true; - } - } - return false; - } finally { - lock.unlock(); - } - } - - /** - * Retains only the elements in this list that are contained in the - * specified collection. In other words, removes from this list all of - * its elements that are not contained in the specified collection. - * - * @param c collection containing elements to be retained in this list - * @return true if this list changed as a result of the call - * @throws ClassCastException if the class of an element of this list - * is incompatible with the specified collection (optional) - * @throws NullPointerException if this list contains a null element and the - * specified collection does not permit null elements (optional), - * or if the specified collection is null - * @see #remove(Object) - */ - public boolean retainAll(Collection c) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - if (len != 0) { - // temp array holds those elements we know we want to keep - int newlen = 0; - Object[] temp = new Object[len]; - for (int i = 0; i < len; ++i) { - Object element = elements[i]; - if (c.contains(element)) - temp[newlen++] = element; - } - if (newlen != len) { - setArray(Arrays.copyOf(temp, newlen)); - return true; - } - } - return false; - } finally { - lock.unlock(); - } - } - - /** - * Appends all of the elements in the specified collection that - * are not already contained in this list, to the end of - * this list, in the order that they are returned by the - * specified collection's iterator. - * - * @param c collection containing elements to be added to this list - * @return the number of elements added - * @throws NullPointerException if the specified collection is null - * @see #addIfAbsent(Object) - */ - public int addAllAbsent(Collection c) { - Object[] cs = c.toArray(); - if (cs.length == 0) - return 0; - Object[] uniq = new Object[cs.length]; - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - int added = 0; - for (int i = 0; i < cs.length; ++i) { // scan for duplicates - Object e = cs[i]; - if (indexOf(e, elements, 0, len) < 0 && - indexOf(e, uniq, 0, added) < 0) - uniq[added++] = e; - } - if (added > 0) { - Object[] newElements = Arrays.copyOf(elements, len + added); - System.arraycopy(uniq, 0, newElements, len, added); - setArray(newElements); - } - return added; - } finally { - lock.unlock(); - } - } - - /** - * Removes all of the elements from this list. - * The list will be empty after this call returns. - */ - public void clear() { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - setArray(new Object[0]); - } finally { - lock.unlock(); - } - } - - /** - * Appends all of the elements in the specified collection to the end - * of this list, in the order that they are returned by the specified - * collection's iterator. - * - * @param c collection containing elements to be added to this list - * @return true if this list changed as a result of the call - * @throws NullPointerException if the specified collection is null - * @see #add(Object) - */ - public boolean addAll(Collection c) { - Object[] cs = c.toArray(); - if (cs.length == 0) - return false; - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - Object[] newElements = Arrays.copyOf(elements, len + cs.length); - System.arraycopy(cs, 0, newElements, len, cs.length); - setArray(newElements); - return true; - } finally { - lock.unlock(); - } - } - - /** - * Inserts all of the elements in the specified collection into this - * list, starting at the specified position. Shifts the element - * currently at that position (if any) and any subsequent elements to - * the right (increases their indices). The new elements will appear - * in this list in the order that they are returned by the - * specified collection's iterator. - * - * @param index index at which to insert the first element - * from the specified collection - * @param c collection containing elements to be added to this list - * @return true if this list changed as a result of the call - * @throws IndexOutOfBoundsException {@inheritDoc} - * @throws NullPointerException if the specified collection is null - * @see #add(int,Object) - */ - public boolean addAll(int index, Collection c) { - Object[] cs = c.toArray(); - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - if (index > len || index < 0) - throw new IndexOutOfBoundsException("Index: "+index+ - ", Size: "+len); - if (cs.length == 0) - return false; - int numMoved = len - index; - Object[] newElements; - if (numMoved == 0) - newElements = Arrays.copyOf(elements, len + cs.length); - else { - newElements = new Object[len + cs.length]; - System.arraycopy(elements, 0, newElements, 0, index); - System.arraycopy(elements, index, - newElements, index + cs.length, - numMoved); - } - System.arraycopy(cs, 0, newElements, index, cs.length); - setArray(newElements); - return true; - } finally { - lock.unlock(); - } - } - - /** - * Save the state of the list to a stream (i.e., serialize it). - * - * @serialData The length of the array backing the list is emitted - * (int), followed by all of its elements (each an Object) - * in the proper order. - * @param s the stream - */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException{ - - // Write out element count, and any hidden stuff - s.defaultWriteObject(); - - Object[] elements = getArray(); - int len = elements.length; - // Write out array length - s.writeInt(len); - - // Write out all elements in the proper order. - for (int i = 0; i < len; i++) - s.writeObject(elements[i]); - } - - /** - * Reconstitute the list from a stream (i.e., deserialize it). - * @param s the stream - */ - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - - // Read in size, and any hidden stuff - s.defaultReadObject(); - -// // bind to new lock -// resetLock(); - - // Read in array length and allocate array - int len = s.readInt(); - Object[] elements = new Object[len]; - - // Read in all elements in the proper order. - for (int i = 0; i < len; i++) - elements[i] = s.readObject(); - setArray(elements); - } - - /** - * Returns a string representation of this list. The string - * representation consists of the string representations of the list's - * elements in the order they are returned by its iterator, enclosed in - * square brackets ("[]"). Adjacent elements are separated by - * the characters ", " (comma and space). Elements are - * converted to strings as by {@link String#valueOf(Object)}. - * - * @return a string representation of this list - */ - public String toString() { - return Arrays.toString(getArray()); - } - - /** - * Compares the specified object with this list for equality. - * Returns {@code true} if the specified object is the same object - * as this object, or if it is also a {@link List} and the sequence - * of elements returned by an {@linkplain List#iterator() iterator} - * over the specified list is the same as the sequence returned by - * an iterator over this list. The two sequences are considered to - * be the same if they have the same length and corresponding - * elements at the same position in the sequence are equal. - * Two elements {@code e1} and {@code e2} are considered - * equal if {@code (e1==null ? e2==null : e1.equals(e2))}. - * - * @param o the object to be compared for equality with this list - * @return {@code true} if the specified object is equal to this list - */ - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof List)) - return false; - - List list = (List)(o); - Iterator it = list.iterator(); - Object[] elements = getArray(); - int len = elements.length; - for (int i = 0; i < len; ++i) - if (!it.hasNext() || !eq(elements[i], it.next())) - return false; - if (it.hasNext()) - return false; - return true; - } - - /** - * Returns the hash code value for this list. - * - *

This implementation uses the definition in {@link List#hashCode}. - * - * @return the hash code value for this list - */ - public int hashCode() { - int hashCode = 1; - Object[] elements = getArray(); - int len = elements.length; - for (int i = 0; i < len; ++i) { - Object obj = elements[i]; - hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode()); - } - return hashCode; + super(); + for (int i = 0, n = toCopyIn.length; i < n; i++) + add(toCopyIn[i]); +// setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); } - /** - * Returns an iterator over the elements in this list in proper sequence. - * - *

The returned iterator provides a snapshot of the state of the list - * when the iterator was constructed. No synchronization is needed while - * traversing the iterator. The iterator does NOT support the - * remove method. - * - * @return an iterator over the elements in this list in proper sequence - */ - public Iterator iterator() { - return new COWIterator(getArray(), 0); - } - - /** - * {@inheritDoc} - * - *

The returned iterator provides a snapshot of the state of the list - * when the iterator was constructed. No synchronization is needed while - * traversing the iterator. The iterator does NOT support the - * remove, set or add methods. - */ - public ListIterator listIterator() { - return new COWIterator(getArray(), 0); - } - - /** - * {@inheritDoc} - * - *

The returned iterator provides a snapshot of the state of the list - * when the iterator was constructed. No synchronization is needed while - * traversing the iterator. The iterator does NOT support the - * remove, set or add methods. - * - * @throws IndexOutOfBoundsException {@inheritDoc} - */ - public ListIterator listIterator(final int index) { - Object[] elements = getArray(); - int len = elements.length; - if (index<0 || index>len) - throw new IndexOutOfBoundsException("Index: "+index); - - return new COWIterator(elements, index); - } - - private static class COWIterator implements ListIterator { - /** Snapshot of the array **/ - private final Object[] snapshot; - /** Index of element to be returned by subsequent call to next. */ - private int cursor; - - private COWIterator(Object[] elements, int initialCursor) { - cursor = initialCursor; - snapshot = elements; - } - - public boolean hasNext() { - return cursor < snapshot.length; - } - - public boolean hasPrevious() { - return cursor > 0; - } - - @SuppressWarnings("unchecked") - public E next() { - if (! hasNext()) - throw new NoSuchElementException(); - return (E) snapshot[cursor++]; - } - - @SuppressWarnings("unchecked") - public E previous() { - if (! hasPrevious()) - throw new NoSuchElementException(); - return (E) snapshot[--cursor]; - } - - public int nextIndex() { - return cursor; - } - - public int previousIndex() { - return cursor-1; - } - - /** - * Not supported. Always throws UnsupportedOperationException. - * @throws UnsupportedOperationException always; remove - * is not supported by this iterator. - */ - public void remove() { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. Always throws UnsupportedOperationException. - * @throws UnsupportedOperationException always; set - * is not supported by this iterator. - */ - public void set(E e) { - throw new UnsupportedOperationException(); - } - - /** - * Not supported. Always throws UnsupportedOperationException. - * @throws UnsupportedOperationException always; add - * is not supported by this iterator. - */ - public void add(E e) { - throw new UnsupportedOperationException(); - } - } - - /** - * Returns a view of the portion of this list between - * fromIndex, inclusive, and toIndex, exclusive. - * The returned list is backed by this list, so changes in the - * returned list are reflected in this list, and vice-versa. - * While mutative operations are supported, they are probably not - * very useful for CopyOnWriteArrayLists. - * - *

The semantics of the list returned by this method become - * undefined if the backing list (i.e., this list) is - * structurally modified in any way other than via the - * returned list. (Structural modifications are those that change - * the size of the list, or otherwise perturb it in such a fashion - * that iterations in progress may yield incorrect results.) - * - * @param fromIndex low endpoint (inclusive) of the subList - * @param toIndex high endpoint (exclusive) of the subList - * @return a view of the specified range within this list - * @throws IndexOutOfBoundsException {@inheritDoc} - */ - public List subList(int fromIndex, int toIndex) { - final ReentrantLock lock = this.lock; - lock.lock(); - try { - Object[] elements = getArray(); - int len = elements.length; - if (fromIndex < 0 || toIndex > len || fromIndex > toIndex) - throw new IndexOutOfBoundsException(); - return new COWSubList(this, fromIndex, toIndex); - } finally { - lock.unlock(); - } - } - - /** - * Sublist for CopyOnWriteArrayList. - * This class extends AbstractList merely for convenience, to - * avoid having to define addAll, etc. This doesn't hurt, but - * is wasteful. This class does not need or use modCount - * mechanics in AbstractList, but does need to check for - * concurrent modification using similar mechanics. On each - * operation, the array that we expect the backing list to use - * is checked and updated. Since we do this for all of the - * base operations invoked by those defined in AbstractList, - * all is well. While inefficient, this is not worth - * improving. The kinds of list operations inherited from - * AbstractList are already so slow on COW sublists that - * adding a bit more space/time doesn't seem even noticeable. - */ - private static class COWSubList - extends AbstractList - implements RandomAccess - { - private final CopyOnWriteArrayList l; - private final int offset; - private int size; - private Object[] expectedArray; - - // only call this holding l's lock - COWSubList(CopyOnWriteArrayList list, - int fromIndex, int toIndex) { - l = list; - expectedArray = l.getArray(); - offset = fromIndex; - size = toIndex - fromIndex; - } - - // only call this holding l's lock - private void checkForComodification() { - if (l.getArray() != expectedArray) - throw new ConcurrentModificationException(); - } - - // only call this holding l's lock - private void rangeCheck(int index) { - if (index<0 || index>=size) - throw new IndexOutOfBoundsException("Index: "+index+ - ",Size: "+size); - } - - public E set(int index, E element) { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - rangeCheck(index); - checkForComodification(); - E x = l.set(index+offset, element); - expectedArray = l.getArray(); - return x; - } finally { - lock.unlock(); - } - } - - public E get(int index) { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - rangeCheck(index); - checkForComodification(); - return l.get(index+offset); - } finally { - lock.unlock(); - } - } - - public int size() { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - checkForComodification(); - return size; - } finally { - lock.unlock(); - } - } - - public void add(int index, E element) { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - checkForComodification(); - if (index<0 || index>size) - throw new IndexOutOfBoundsException(); - l.add(index+offset, element); - expectedArray = l.getArray(); - size++; - } finally { - lock.unlock(); - } - } - - public void clear() { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - checkForComodification(); - l.removeRange(offset, offset+size); - expectedArray = l.getArray(); - size = 0; - } finally { - lock.unlock(); - } - } - - public E remove(int index) { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - rangeCheck(index); - checkForComodification(); - E result = l.remove(index+offset); - expectedArray = l.getArray(); - size--; - return result; - } finally { - lock.unlock(); - } - } - - public boolean remove(Object o) { - int index = indexOf(o); - if (index == -1) - return false; - remove(index); - return true; - } - - public Iterator iterator() { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - checkForComodification(); - return new COWSubListIterator(l, 0, offset, size); - } finally { - lock.unlock(); - } - } - - public ListIterator listIterator(final int index) { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - checkForComodification(); - if (index<0 || index>size) - throw new IndexOutOfBoundsException("Index: "+index+ - ", Size: "+size); - return new COWSubListIterator(l, index, offset, size); - } finally { - lock.unlock(); - } - } - - public List subList(int fromIndex, int toIndex) { - final ReentrantLock lock = l.lock; - lock.lock(); - try { - checkForComodification(); - if (fromIndex<0 || toIndex>size) - throw new IndexOutOfBoundsException(); - return new COWSubList(l, fromIndex + offset, - toIndex + offset); - } finally { - lock.unlock(); - } - } - - } - - - private static class COWSubListIterator implements ListIterator { - private final ListIterator i; - private final int index; - private final int offset; - private final int size; - - COWSubListIterator(List l, int index, int offset, - int size) { - this.index = index; - this.offset = offset; - this.size = size; - i = l.listIterator(index+offset); - } - - public boolean hasNext() { - return nextIndex() < size; - } - - public E next() { - if (hasNext()) - return i.next(); - else - throw new NoSuchElementException(); - } - - public boolean hasPrevious() { - return previousIndex() >= 0; - } - - public E previous() { - if (hasPrevious()) - return i.previous(); - else - throw new NoSuchElementException(); - } - - public int nextIndex() { - return i.nextIndex() - offset; - } - - public int previousIndex() { - return i.previousIndex() - offset; - } - - public void remove() { - throw new UnsupportedOperationException(); - } - - public void set(E e) { - throw new UnsupportedOperationException(); - } - - public void add(E e) { - throw new UnsupportedOperationException(); - } - } - - // Support for resetting lock while deserializing -// private static final Unsafe unsafe = Unsafe.getUnsafe(); -// private static final long lockOffset; -// static { +// /** +// * Returns the number of elements in this list. +// * +// * @return the number of elements in this list +// */ +// public int size() { +// return getArray().length; +// } +// +// /** +// * Returns true if this list contains no elements. +// * +// * @return true if this list contains no elements +// */ +// public boolean isEmpty() { +// return size() == 0; +// } +// +// /** +// * Test for equality, coping with nulls. +// */ +// private static boolean eq(Object o1, Object o2) { +// return (o1 == null ? o2 == null : o1.equals(o2)); +// } +// +// /** +// * static version of indexOf, to allow repeated calls without +// * needing to re-acquire array each time. +// * @param o element to search for +// * @param elements the array +// * @param index first index to search +// * @param fence one past last index to search +// * @return index of element, or -1 if absent +// */ +// private static int indexOf(Object o, Object[] elements, +// int index, int fence) { +// if (o == null) { +// for (int i = index; i < fence; i++) +// if (elements[i] == null) +// return i; +// } else { +// for (int i = index; i < fence; i++) +// if (o.equals(elements[i])) +// return i; +// } +// return -1; +// } +// +// /** +// * static version of lastIndexOf. +// * @param o element to search for +// * @param elements the array +// * @param index first index to search +// * @return index of element, or -1 if absent +// */ +// private static int lastIndexOf(Object o, Object[] elements, int index) { +// if (o == null) { +// for (int i = index; i >= 0; i--) +// if (elements[i] == null) +// return i; +// } else { +// for (int i = index; i >= 0; i--) +// if (o.equals(elements[i])) +// return i; +// } +// return -1; +// } +// +// /** +// * Returns true if this list contains the specified element. +// * More formally, returns true if and only if this list contains +// * at least one element e such that +// * (o==null ? e==null : o.equals(e)). +// * +// * @param o element whose presence in this list is to be tested +// * @return true if this list contains the specified element +// */ +// public boolean contains(Object o) { +// Object[] elements = getArray(); +// return indexOf(o, elements, 0, elements.length) >= 0; +// } +// +// /** +// * {@inheritDoc} +// */ +// public int indexOf(Object o) { +// Object[] elements = getArray(); +// return indexOf(o, elements, 0, elements.length); +// } +// +// /** +// * Returns the index of the first occurrence of the specified element in +// * this list, searching forwards from index, or returns -1 if +// * the element is not found. +// * More formally, returns the lowest index i such that +// * (i >= index && (e==null ? get(i)==null : e.equals(get(i)))), +// * or -1 if there is no such index. +// * +// * @param e element to search for +// * @param index index to start searching from +// * @return the index of the first occurrence of the element in +// * this list at position index or later in the list; +// * -1 if the element is not found. +// * @throws IndexOutOfBoundsException if the specified index is negative +// */ +// public int indexOf(E e, int index) { +// Object[] elements = getArray(); +// return indexOf(e, elements, index, elements.length); +// } +// +// /** +// * {@inheritDoc} +// */ +// public int lastIndexOf(Object o) { +// Object[] elements = getArray(); +// return lastIndexOf(o, elements, elements.length - 1); +// } +// +// /** +// * Returns the index of the last occurrence of the specified element in +// * this list, searching backwards from index, or returns -1 if +// * the element is not found. +// * More formally, returns the highest index i such that +// * (i <= index && (e==null ? get(i)==null : e.equals(get(i)))), +// * or -1 if there is no such index. +// * +// * @param e element to search for +// * @param index index to start searching backwards from +// * @return the index of the last occurrence of the element at position +// * less than or equal to index in this list; +// * -1 if the element is not found. +// * @throws IndexOutOfBoundsException if the specified index is greater +// * than or equal to the current size of this list +// */ +// public int lastIndexOf(E e, int index) { +// Object[] elements = getArray(); +// return lastIndexOf(e, elements, index); +// } +// +// /** +// * Returns a shallow copy of this list. (The elements themselves +// * are not copied.) +// * +// * @return a clone of this list +// */ +// public Object clone() { // try { -// lockOffset = unsafe.objectFieldOffset -// (CopyOnWriteArrayList.class.getDeclaredField("lock")); -// } catch (Exception ex) { throw new Error(ex); } +// CopyOnWriteArrayList c = (CopyOnWriteArrayList)(super.clone()); +//// c.resetLock(); +// return c; +// } catch (CloneNotSupportedException e) { +// // this shouldn't happen, since we are Cloneable +// throw new InternalError(); +// } +// } +// +// /** +// * Returns an array containing all of the elements in this list +// * in proper sequence (from first to last element). +// * +// *

The returned array will be "safe" in that no references to it are +// * maintained by this list. (In other words, this method must allocate +// * a new array). The caller is thus free to modify the returned array. +// * +// *

This method acts as bridge between array-based and collection-based +// * APIs. +// * +// * @return an array containing all the elements in this list +// */ +// public Object[] toArray() { +// Object[] elements = getArray(); +// return Arrays.copyOf(elements, elements.length); +// } +// +// /** +// * Returns an array containing all of the elements in this list in +// * proper sequence (from first to last element); the runtime type of +// * the returned array is that of the specified array. If the list fits +// * in the specified array, it is returned therein. Otherwise, a new +// * array is allocated with the runtime type of the specified array and +// * the size of this list. +// * +// *

If this list fits in the specified array with room to spare +// * (i.e., the array has more elements than this list), the element in +// * the array immediately following the end of the list is set to +// * null. (This is useful in determining the length of this +// * list only if the caller knows that this list does not contain +// * any null elements.) +// * +// *

Like the {@link #toArray()} method, this method acts as bridge between +// * array-based and collection-based APIs. Further, this method allows +// * precise control over the runtime type of the output array, and may, +// * under certain circumstances, be used to save allocation costs. +// * +// *

Suppose x is a list known to contain only strings. +// * The following code can be used to dump the list into a newly +// * allocated array of String: +// * +// *

+//     *     String[] y = x.toArray(new String[0]);
+// * +// * Note that toArray(new Object[0]) is identical in function to +// * toArray(). +// * +// * @param a the array into which the elements of the list are to +// * be stored, if it is big enough; otherwise, a new array of the +// * same runtime type is allocated for this purpose. +// * @return an array containing all the elements in this list +// * @throws ArrayStoreException if the runtime type of the specified array +// * is not a supertype of the runtime type of every element in +// * this list +// * @throws NullPointerException if the specified array is null +// */ +// @SuppressWarnings("unchecked") +// public T[] toArray(T a[]) { +// Object[] elements = getArray(); +// int len = elements.length; +// if (a.length < len) +// return (T[]) Arrays.copyOf(elements, len, a.getClass()); +// else { +// System.arraycopy(elements, 0, a, 0, len); +// if (a.length > len) +// a[len] = null; +// return a; +// } +// } +// +// // Positional Access Operations +// +// @SuppressWarnings("unchecked") +// private E get(Object[] a, int index) { +// return (E) a[index]; +// } +// +// /** +// * {@inheritDoc} +// * +// * @throws IndexOutOfBoundsException {@inheritDoc} +// */ +// public E get(int index) { +// return get(getArray(), index); +// } +// +// /** +// * Replaces the element at the specified position in this list with the +// * specified element. +// * +// * @throws IndexOutOfBoundsException {@inheritDoc} +// */ +// public E set(int index, E element) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// E oldValue = get(elements, index); +// +// if (oldValue != element) { +// int len = elements.length; +// Object[] newElements = Arrays.copyOf(elements, len); +// newElements[index] = element; +// setArray(newElements); +// } else { +// // Not quite a no-op; ensures volatile write semantics +// setArray(elements); +// } +// return oldValue; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Appends the specified element to the end of this list. +// * +// * @param e element to be appended to this list +// * @return true (as specified by {@link Collection#add}) +// */ +// public boolean add(E e) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// Object[] newElements = Arrays.copyOf(elements, len + 1); +// newElements[len] = e; +// setArray(newElements); +// return true; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Inserts the specified element at the specified position in this +// * list. Shifts the element currently at that position (if any) and +// * any subsequent elements to the right (adds one to their indices). +// * +// * @throws IndexOutOfBoundsException {@inheritDoc} +// */ +// public void add(int index, E element) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// if (index > len || index < 0) +// throw new IndexOutOfBoundsException("Index: "+index+ +// ", Size: "+len); +// Object[] newElements; +// int numMoved = len - index; +// if (numMoved == 0) +// newElements = Arrays.copyOf(elements, len + 1); +// else { +// newElements = new Object[len + 1]; +// System.arraycopy(elements, 0, newElements, 0, index); +// System.arraycopy(elements, index, newElements, index + 1, +// numMoved); +// } +// newElements[index] = element; +// setArray(newElements); +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Removes the element at the specified position in this list. +// * Shifts any subsequent elements to the left (subtracts one from their +// * indices). Returns the element that was removed from the list. +// * +// * @throws IndexOutOfBoundsException {@inheritDoc} +// */ +// public E remove(int index) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// E oldValue = get(elements, index); +// int numMoved = len - index - 1; +// if (numMoved == 0) +// setArray(Arrays.copyOf(elements, len - 1)); +// else { +// Object[] newElements = new Object[len - 1]; +// System.arraycopy(elements, 0, newElements, 0, index); +// System.arraycopy(elements, index + 1, newElements, index, +// numMoved); +// setArray(newElements); +// } +// return oldValue; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Removes the first occurrence of the specified element from this list, +// * if it is present. If this list does not contain the element, it is +// * unchanged. More formally, removes the element with the lowest index +// * i such that +// * (o==null ? get(i)==null : o.equals(get(i))) +// * (if such an element exists). Returns true if this list +// * contained the specified element (or equivalently, if this list +// * changed as a result of the call). +// * +// * @param o element to be removed from this list, if present +// * @return true if this list contained the specified element +// */ +// public boolean remove(Object o) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// if (len != 0) { +// // Copy while searching for element to remove +// // This wins in the normal case of element being present +// int newlen = len - 1; +// Object[] newElements = new Object[newlen]; +// +// for (int i = 0; i < newlen; ++i) { +// if (eq(o, elements[i])) { +// // found one; copy remaining and exit +// for (int k = i + 1; k < len; ++k) +// newElements[k-1] = elements[k]; +// setArray(newElements); +// return true; +// } else +// newElements[i] = elements[i]; +// } +// +// // special handling for last cell +// if (eq(o, elements[newlen])) { +// setArray(newElements); +// return true; +// } +// } +// return false; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Removes from this list all of the elements whose index is between +// * fromIndex, inclusive, and toIndex, exclusive. +// * Shifts any succeeding elements to the left (reduces their index). +// * This call shortens the list by (toIndex - fromIndex) elements. +// * (If toIndex==fromIndex, this operation has no effect.) +// * +// * @param fromIndex index of first element to be removed +// * @param toIndex index after last element to be removed +// * @throws IndexOutOfBoundsException if fromIndex or toIndex out of range +// * (@code{fromIndex < 0 || toIndex > size() || toIndex < fromIndex}) +// */ +// private void removeRange(int fromIndex, int toIndex) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// +// if (fromIndex < 0 || toIndex > len || toIndex < fromIndex) +// throw new IndexOutOfBoundsException(); +// int newlen = len - (toIndex - fromIndex); +// int numMoved = len - toIndex; +// if (numMoved == 0) +// setArray(Arrays.copyOf(elements, newlen)); +// else { +// Object[] newElements = new Object[newlen]; +// System.arraycopy(elements, 0, newElements, 0, fromIndex); +// System.arraycopy(elements, toIndex, newElements, +// fromIndex, numMoved); +// setArray(newElements); +// } +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Append the element if not present. +// * +// * @param e element to be added to this list, if absent +// * @return true if the element was added +// */ +// public boolean addIfAbsent(E e) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// // Copy while checking if already present. +// // This wins in the most common case where it is not present +// Object[] elements = getArray(); +// int len = elements.length; +// Object[] newElements = new Object[len + 1]; +// for (int i = 0; i < len; ++i) { +// if (eq(e, elements[i])) +// return false; // exit, throwing away copy +// else +// newElements[i] = elements[i]; +// } +// newElements[len] = e; +// setArray(newElements); +// return true; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Returns true if this list contains all of the elements of the +// * specified collection. +// * +// * @param c collection to be checked for containment in this list +// * @return true if this list contains all of the elements of the +// * specified collection +// * @throws NullPointerException if the specified collection is null +// * @see #contains(Object) +// */ +// public boolean containsAll(Collection c) { +// Object[] elements = getArray(); +// int len = elements.length; +// for (Object e : c) { +// if (indexOf(e, elements, 0, len) < 0) +// return false; +// } +// return true; +// } +// +// /** +// * Removes from this list all of its elements that are contained in +// * the specified collection. This is a particularly expensive operation +// * in this class because of the need for an internal temporary array. +// * +// * @param c collection containing elements to be removed from this list +// * @return true if this list changed as a result of the call +// * @throws ClassCastException if the class of an element of this list +// * is incompatible with the specified collection (optional) +// * @throws NullPointerException if this list contains a null element and the +// * specified collection does not permit null elements (optional), +// * or if the specified collection is null +// * @see #remove(Object) +// */ +// public boolean removeAll(Collection c) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// if (len != 0) { +// // temp array holds those elements we know we want to keep +// int newlen = 0; +// Object[] temp = new Object[len]; +// for (int i = 0; i < len; ++i) { +// Object element = elements[i]; +// if (!c.contains(element)) +// temp[newlen++] = element; +// } +// if (newlen != len) { +// setArray(Arrays.copyOf(temp, newlen)); +// return true; +// } +// } +// return false; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Retains only the elements in this list that are contained in the +// * specified collection. In other words, removes from this list all of +// * its elements that are not contained in the specified collection. +// * +// * @param c collection containing elements to be retained in this list +// * @return true if this list changed as a result of the call +// * @throws ClassCastException if the class of an element of this list +// * is incompatible with the specified collection (optional) +// * @throws NullPointerException if this list contains a null element and the +// * specified collection does not permit null elements (optional), +// * or if the specified collection is null +// * @see #remove(Object) +// */ +// public boolean retainAll(Collection c) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// if (len != 0) { +// // temp array holds those elements we know we want to keep +// int newlen = 0; +// Object[] temp = new Object[len]; +// for (int i = 0; i < len; ++i) { +// Object element = elements[i]; +// if (c.contains(element)) +// temp[newlen++] = element; +// } +// if (newlen != len) { +// setArray(Arrays.copyOf(temp, newlen)); +// return true; +// } +// } +// return false; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Appends all of the elements in the specified collection that +// * are not already contained in this list, to the end of +// * this list, in the order that they are returned by the +// * specified collection's iterator. +// * +// * @param c collection containing elements to be added to this list +// * @return the number of elements added +// * @throws NullPointerException if the specified collection is null +// * @see #addIfAbsent(Object) +// */ +// public int addAllAbsent(Collection c) { +// Object[] cs = c.toArray(); +// if (cs.length == 0) +// return 0; +// Object[] uniq = new Object[cs.length]; +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// int added = 0; +// for (int i = 0; i < cs.length; ++i) { // scan for duplicates +// Object e = cs[i]; +// if (indexOf(e, elements, 0, len) < 0 && +// indexOf(e, uniq, 0, added) < 0) +// uniq[added++] = e; +// } +// if (added > 0) { +// Object[] newElements = Arrays.copyOf(elements, len + added); +// System.arraycopy(uniq, 0, newElements, len, added); +// setArray(newElements); +// } +// return added; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Removes all of the elements from this list. +// * The list will be empty after this call returns. +// */ +// public void clear() { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// setArray(new Object[0]); +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Appends all of the elements in the specified collection to the end +// * of this list, in the order that they are returned by the specified +// * collection's iterator. +// * +// * @param c collection containing elements to be added to this list +// * @return true if this list changed as a result of the call +// * @throws NullPointerException if the specified collection is null +// * @see #add(Object) +// */ +// public boolean addAll(Collection c) { +// Object[] cs = c.toArray(); +// if (cs.length == 0) +// return false; +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// Object[] newElements = Arrays.copyOf(elements, len + cs.length); +// System.arraycopy(cs, 0, newElements, len, cs.length); +// setArray(newElements); +// return true; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Inserts all of the elements in the specified collection into this +// * list, starting at the specified position. Shifts the element +// * currently at that position (if any) and any subsequent elements to +// * the right (increases their indices). The new elements will appear +// * in this list in the order that they are returned by the +// * specified collection's iterator. +// * +// * @param index index at which to insert the first element +// * from the specified collection +// * @param c collection containing elements to be added to this list +// * @return true if this list changed as a result of the call +// * @throws IndexOutOfBoundsException {@inheritDoc} +// * @throws NullPointerException if the specified collection is null +// * @see #add(int,Object) +// */ +// public boolean addAll(int index, Collection c) { +// Object[] cs = c.toArray(); +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// if (index > len || index < 0) +// throw new IndexOutOfBoundsException("Index: "+index+ +// ", Size: "+len); +// if (cs.length == 0) +// return false; +// int numMoved = len - index; +// Object[] newElements; +// if (numMoved == 0) +// newElements = Arrays.copyOf(elements, len + cs.length); +// else { +// newElements = new Object[len + cs.length]; +// System.arraycopy(elements, 0, newElements, 0, index); +// System.arraycopy(elements, index, +// newElements, index + cs.length, +// numMoved); +// } +// System.arraycopy(cs, 0, newElements, index, cs.length); +// setArray(newElements); +// return true; +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Save the state of the list to a stream (i.e., serialize it). +// * +// * @serialData The length of the array backing the list is emitted +// * (int), followed by all of its elements (each an Object) +// * in the proper order. +// * @param s the stream +// */ +// private void writeObject(java.io.ObjectOutputStream s) +// throws java.io.IOException{ +// +// // Write out element count, and any hidden stuff +// s.defaultWriteObject(); +// +// Object[] elements = getArray(); +// int len = elements.length; +// // Write out array length +// s.writeInt(len); +// +// // Write out all elements in the proper order. +// for (int i = 0; i < len; i++) +// s.writeObject(elements[i]); +// } +// +// /** +// * Reconstitute the list from a stream (i.e., deserialize it). +// * @param s the stream +// */ +// private void readObject(java.io.ObjectInputStream s) +// throws java.io.IOException, ClassNotFoundException { +// +// // Read in size, and any hidden stuff +// s.defaultReadObject(); +// +//// // bind to new lock +//// resetLock(); +// +// // Read in array length and allocate array +// int len = s.readInt(); +// Object[] elements = new Object[len]; +// +// // Read in all elements in the proper order. +// for (int i = 0; i < len; i++) +// elements[i] = s.readObject(); +// setArray(elements); +// } +// +// /** +// * Returns a string representation of this list. The string +// * representation consists of the string representations of the list's +// * elements in the order they are returned by its iterator, enclosed in +// * square brackets ("[]"). Adjacent elements are separated by +// * the characters ", " (comma and space). Elements are +// * converted to strings as by {@link String#valueOf(Object)}. +// * +// * @return a string representation of this list +// */ +// public String toString() { +// return Arrays.toString(getArray()); +// } +// +// /** +// * Compares the specified object with this list for equality. +// * Returns {@code true} if the specified object is the same object +// * as this object, or if it is also a {@link List} and the sequence +// * of elements returned by an {@linkplain List#iterator() iterator} +// * over the specified list is the same as the sequence returned by +// * an iterator over this list. The two sequences are considered to +// * be the same if they have the same length and corresponding +// * elements at the same position in the sequence are equal. +// * Two elements {@code e1} and {@code e2} are considered +// * equal if {@code (e1==null ? e2==null : e1.equals(e2))}. +// * +// * @param o the object to be compared for equality with this list +// * @return {@code true} if the specified object is equal to this list +// */ +// public boolean equals(Object o) { +// if (o == this) +// return true; +// if (!(o instanceof List)) +// return false; +// +// List list = (List)(o); +// Iterator it = list.iterator(); +// Object[] elements = getArray(); +// int len = elements.length; +// for (int i = 0; i < len; ++i) +// if (!it.hasNext() || !eq(elements[i], it.next())) +// return false; +// if (it.hasNext()) +// return false; +// return true; +// } +// +// /** +// * Returns the hash code value for this list. +// * +// *

This implementation uses the definition in {@link List#hashCode}. +// * +// * @return the hash code value for this list +// */ +// public int hashCode() { +// int hashCode = 1; +// Object[] elements = getArray(); +// int len = elements.length; +// for (int i = 0; i < len; ++i) { +// Object obj = elements[i]; +// hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode()); +// } +// return hashCode; +// } +// +// /** +// * Returns an iterator over the elements in this list in proper sequence. +// * +// *

The returned iterator provides a snapshot of the state of the list +// * when the iterator was constructed. No synchronization is needed while +// * traversing the iterator. The iterator does NOT support the +// * remove method. +// * +// * @return an iterator over the elements in this list in proper sequence +// */ +// public Iterator iterator() { +// return new COWIterator(getArray(), 0); +// } +// +// /** +// * {@inheritDoc} +// * +// *

The returned iterator provides a snapshot of the state of the list +// * when the iterator was constructed. No synchronization is needed while +// * traversing the iterator. The iterator does NOT support the +// * remove, set or add methods. +// */ +// public ListIterator listIterator() { +// return new COWIterator(getArray(), 0); +// } +// +// /** +// * {@inheritDoc} +// * +// *

The returned iterator provides a snapshot of the state of the list +// * when the iterator was constructed. No synchronization is needed while +// * traversing the iterator. The iterator does NOT support the +// * remove, set or add methods. +// * +// * @throws IndexOutOfBoundsException {@inheritDoc} +// */ +// public ListIterator listIterator(final int index) { +// Object[] elements = getArray(); +// int len = elements.length; +// if (index<0 || index>len) +// throw new IndexOutOfBoundsException("Index: "+index); +// +// return new COWIterator(elements, index); +// } +// +// private static class COWIterator implements ListIterator { +// /** Snapshot of the array **/ +// private final Object[] snapshot; +// /** Index of element to be returned by subsequent call to next. */ +// private int cursor; +// +// private COWIterator(Object[] elements, int initialCursor) { +// cursor = initialCursor; +// snapshot = elements; +// } +// +// public boolean hasNext() { +// return cursor < snapshot.length; +// } +// +// public boolean hasPrevious() { +// return cursor > 0; +// } +// +// @SuppressWarnings("unchecked") +// public E next() { +// if (! hasNext()) +// throw new NoSuchElementException(); +// return (E) snapshot[cursor++]; +// } +// +// @SuppressWarnings("unchecked") +// public E previous() { +// if (! hasPrevious()) +// throw new NoSuchElementException(); +// return (E) snapshot[--cursor]; +// } +// +// public int nextIndex() { +// return cursor; +// } +// +// public int previousIndex() { +// return cursor-1; +// } +// +// /** +// * Not supported. Always throws UnsupportedOperationException. +// * @throws UnsupportedOperationException always; remove +// * is not supported by this iterator. +// */ +// public void remove() { +// throw new UnsupportedOperationException(); +// } +// +// /** +// * Not supported. Always throws UnsupportedOperationException. +// * @throws UnsupportedOperationException always; set +// * is not supported by this iterator. +// */ +// public void set(E e) { +// throw new UnsupportedOperationException(); +// } +// +// /** +// * Not supported. Always throws UnsupportedOperationException. +// * @throws UnsupportedOperationException always; add +// * is not supported by this iterator. +// */ +// public void add(E e) { +// throw new UnsupportedOperationException(); +// } +// } +// +// /** +// * Returns a view of the portion of this list between +// * fromIndex, inclusive, and toIndex, exclusive. +// * The returned list is backed by this list, so changes in the +// * returned list are reflected in this list, and vice-versa. +// * While mutative operations are supported, they are probably not +// * very useful for CopyOnWriteArrayLists. +// * +// *

The semantics of the list returned by this method become +// * undefined if the backing list (i.e., this list) is +// * structurally modified in any way other than via the +// * returned list. (Structural modifications are those that change +// * the size of the list, or otherwise perturb it in such a fashion +// * that iterations in progress may yield incorrect results.) +// * +// * @param fromIndex low endpoint (inclusive) of the subList +// * @param toIndex high endpoint (exclusive) of the subList +// * @return a view of the specified range within this list +// * @throws IndexOutOfBoundsException {@inheritDoc} +// */ +// public List subList(int fromIndex, int toIndex) { +// final ReentrantLock lock = this.lock; +// lock.lock(); +// try { +// Object[] elements = getArray(); +// int len = elements.length; +// if (fromIndex < 0 || toIndex > len || fromIndex > toIndex) +// throw new IndexOutOfBoundsException(); +// return new COWSubList(this, fromIndex, toIndex); +// } finally { +// lock.unlock(); +// } +// } +// +// /** +// * Sublist for CopyOnWriteArrayList. +// * This class extends AbstractList merely for convenience, to +// * avoid having to define addAll, etc. This doesn't hurt, but +// * is wasteful. This class does not need or use modCount +// * mechanics in AbstractList, but does need to check for +// * concurrent modification using similar mechanics. On each +// * operation, the array that we expect the backing list to use +// * is checked and updated. Since we do this for all of the +// * base operations invoked by those defined in AbstractList, +// * all is well. While inefficient, this is not worth +// * improving. The kinds of list operations inherited from +// * AbstractList are already so slow on COW sublists that +// * adding a bit more space/time doesn't seem even noticeable. +// */ +// private static class COWSubList +// extends AbstractList +// implements RandomAccess +// { +// private final CopyOnWriteArrayList l; +// private final int offset; +// private int size; +// private Object[] expectedArray; +// +// // only call this holding l's lock +// COWSubList(CopyOnWriteArrayList list, +// int fromIndex, int toIndex) { +// l = list; +// expectedArray = l.getArray(); +// offset = fromIndex; +// size = toIndex - fromIndex; +// } +// +// // only call this holding l's lock +// private void checkForComodification() { +// if (l.getArray() != expectedArray) +// throw new ConcurrentModificationException(); +// } +// +// // only call this holding l's lock +// private void rangeCheck(int index) { +// if (index<0 || index>=size) +// throw new IndexOutOfBoundsException("Index: "+index+ +// ",Size: "+size); +// } +// +// public E set(int index, E element) { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// rangeCheck(index); +// checkForComodification(); +// E x = l.set(index+offset, element); +// expectedArray = l.getArray(); +// return x; +// } finally { +// lock.unlock(); +// } +// } +// +// public E get(int index) { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// rangeCheck(index); +// checkForComodification(); +// return l.get(index+offset); +// } finally { +// lock.unlock(); +// } +// } +// +// public int size() { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// checkForComodification(); +// return size; +// } finally { +// lock.unlock(); +// } +// } +// +// public void add(int index, E element) { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// checkForComodification(); +// if (index<0 || index>size) +// throw new IndexOutOfBoundsException(); +// l.add(index+offset, element); +// expectedArray = l.getArray(); +// size++; +// } finally { +// lock.unlock(); +// } +// } +// +// public void clear() { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// checkForComodification(); +// l.removeRange(offset, offset+size); +// expectedArray = l.getArray(); +// size = 0; +// } finally { +// lock.unlock(); +// } +// } +// +// public E remove(int index) { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// rangeCheck(index); +// checkForComodification(); +// E result = l.remove(index+offset); +// expectedArray = l.getArray(); +// size--; +// return result; +// } finally { +// lock.unlock(); +// } +// } +// +// public boolean remove(Object o) { +// int index = indexOf(o); +// if (index == -1) +// return false; +// remove(index); +// return true; +// } +// +// public Iterator iterator() { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// checkForComodification(); +// return new COWSubListIterator(l, 0, offset, size); +// } finally { +// lock.unlock(); +// } +// } +// +// public ListIterator listIterator(final int index) { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// checkForComodification(); +// if (index<0 || index>size) +// throw new IndexOutOfBoundsException("Index: "+index+ +// ", Size: "+size); +// return new COWSubListIterator(l, index, offset, size); +// } finally { +// lock.unlock(); +// } +// } +// +// public List subList(int fromIndex, int toIndex) { +// final ReentrantLock lock = l.lock; +// lock.lock(); +// try { +// checkForComodification(); +// if (fromIndex<0 || toIndex>size) +// throw new IndexOutOfBoundsException(); +// return new COWSubList(l, fromIndex + offset, +// toIndex + offset); +// } finally { +// lock.unlock(); +// } +// } +// // } -// private void resetLock() { -// unsafe.putObjectVolatile(this, lockOffset, new ReentrantLock()); +// +// +// private static class COWSubListIterator implements ListIterator { +// private final ListIterator i; +// private final int index; +// private final int offset; +// private final int size; +// +// COWSubListIterator(List l, int index, int offset, +// int size) { +// this.index = index; +// this.offset = offset; +// this.size = size; +// i = l.listIterator(index+offset); +// } +// +// public boolean hasNext() { +// return nextIndex() < size; +// } +// +// public E next() { +// if (hasNext()) +// return i.next(); +// else +// throw new NoSuchElementException(); +// } +// +// public boolean hasPrevious() { +// return previousIndex() >= 0; +// } +// +// public E previous() { +// if (hasPrevious()) +// return i.previous(); +// else +// throw new NoSuchElementException(); +// } +// +// public int nextIndex() { +// return i.nextIndex() - offset; +// } +// +// public int previousIndex() { +// return i.previousIndex() - offset; +// } +// +// public void remove() { +// throw new UnsupportedOperationException(); +// } +// +// public void set(E e) { +// throw new UnsupportedOperationException(); +// } +// +// public void add(E e) { +// throw new UnsupportedOperationException(); +// } // } +// +// // Support for resetting lock while deserializing +//// private static final Unsafe unsafe = Unsafe.getUnsafe(); +//// private static final long lockOffset; +//// static { +//// try { +//// lockOffset = unsafe.objectFieldOffset +//// (CopyOnWriteArrayList.class.getDeclaredField("lock")); +//// } catch (Exception ex) { throw new Error(ex); } +//// } +//// private void resetLock() { +//// unsafe.putObjectVolatile(this, lockOffset, new ReentrantLock()); +//// } } diff --git a/sources/net.sf.j2s.java.core/src/java/util/concurrent/CopyOnWriteArraySet.java b/sources/net.sf.j2s.java.core/src/java/util/concurrent/CopyOnWriteArraySet.java index 9114d45fb..1a6506800 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/concurrent/CopyOnWriteArraySet.java +++ b/sources/net.sf.j2s.java.core/src/java/util/concurrent/CopyOnWriteArraySet.java @@ -36,68 +36,70 @@ package java.util.concurrent; import java.util.*; -/** - * A {@link java.util.Set} that uses an internal {@link CopyOnWriteArrayList} - * for all of its operations. Thus, it shares the same basic properties: - *

    - *
  • It is best suited for applications in which set sizes generally - * stay small, read-only operations - * vastly outnumber mutative operations, and you need - * to prevent interference among threads during traversal. - *
  • It is thread-safe. - *
  • Mutative operations (add, set, remove, etc.) - * are expensive since they usually entail copying the entire underlying - * array. - *
  • Iterators do not support the mutative remove operation. - *
  • Traversal via iterators is fast and cannot encounter - * interference from other threads. Iterators rely on - * unchanging snapshots of the array at the time the iterators were - * constructed. - *
- * - *

Sample Usage. The following code sketch uses a - * copy-on-write set to maintain a set of Handler objects that - * perform some action upon state updates. - * - *

- * class Handler { void handle(); ... }
- *
- * class X {
- *    private final CopyOnWriteArraySet<Handler> handlers
- *       = new CopyOnWriteArraySet<Handler>();
- *    public void addHandler(Handler h) { handlers.add(h); }
- *
- *    private long internalState;
- *    private synchronized void changeState() { internalState = ...; }
- *
- *    public void update() {
- *       changeState();
- *       for (Handler handler : handlers)
- *          handler.handle();
- *    }
- * }
- * 
- * - *

This class is a member of the - * - * Java Collections Framework. - * - * @see CopyOnWriteArrayList - * @since 1.5 - * @author Doug Lea - * @param the type of elements held in this collection - */ -public class CopyOnWriteArraySet extends AbstractSet - implements java.io.Serializable { - private static final long serialVersionUID = 5457747651344034263L; - - private final CopyOnWriteArrayList al; - +///** +// * A {@link java.util.Set} that uses an internal {@link CopyOnWriteArrayList} +// * for all of its operations. Thus, it shares the same basic properties: +// *

    +// *
  • It is best suited for applications in which set sizes generally +// * stay small, read-only operations +// * vastly outnumber mutative operations, and you need +// * to prevent interference among threads during traversal. +// *
  • It is thread-safe. +// *
  • Mutative operations (add, set, remove, etc.) +// * are expensive since they usually entail copying the entire underlying +// * array. +// *
  • Iterators do not support the mutative remove operation. +// *
  • Traversal via iterators is fast and cannot encounter +// * interference from other threads. Iterators rely on +// * unchanging snapshots of the array at the time the iterators were +// * constructed. +// *
+// * +// *

Sample Usage. The following code sketch uses a +// * copy-on-write set to maintain a set of Handler objects that +// * perform some action upon state updates. +// * +// *

+// * class Handler { void handle(); ... }
+// *
+// * class X {
+// *    private final CopyOnWriteArraySet<Handler> handlers
+// *       = new CopyOnWriteArraySet<Handler>();
+// *    public void addHandler(Handler h) { handlers.add(h); }
+// *
+// *    private long internalState;
+// *    private synchronized void changeState() { internalState = ...; }
+// *
+// *    public void update() {
+// *       changeState();
+// *       for (Handler handler : handlers)
+// *          handler.handle();
+// *    }
+// * }
+// * 
+// * +// *

This class is a member of the +// * +// * Java Collections Framework. +// * +// * @see CopyOnWriteArrayList +// * @since 1.5 +// * @author Doug Lea +// * @param the type of elements held in this collection +// */ +public class CopyOnWriteArraySet extends HashSet { +// +//AbstractSet +// implements java.io.Serializable { +// private static final long serialVersionUID = 5457747651344034263L; +// +// private final CopyOnWriteArrayList al; +// /** * Creates an empty set. */ public CopyOnWriteArraySet() { - al = new CopyOnWriteArrayList(); +// al = new CopyOnWriteArrayList(); } /** @@ -108,286 +110,287 @@ public CopyOnWriteArraySet() { * @throws NullPointerException if the specified collection is null */ public CopyOnWriteArraySet(Collection c) { - al = new CopyOnWriteArrayList(); - al.addAllAbsent(c); - } - - /** - * Returns the number of elements in this set. - * - * @return the number of elements in this set - */ - public int size() { - return al.size(); - } - - /** - * Returns true if this set contains no elements. - * - * @return true if this set contains no elements - */ - public boolean isEmpty() { - return al.isEmpty(); - } - - /** - * Returns true if this set contains the specified element. - * More formally, returns true if and only if this set - * contains an element e such that - * (o==null ? e==null : o.equals(e)). - * - * @param o element whose presence in this set is to be tested - * @return true if this set contains the specified element - */ - public boolean contains(Object o) { - return al.contains(o); - } - - /** - * Returns an array containing all of the elements in this set. - * If this set makes any guarantees as to what order its elements - * are returned by its iterator, this method must return the - * elements in the same order. - * - *

The returned array will be "safe" in that no references to it - * are maintained by this set. (In other words, this method must - * allocate a new array even if this set is backed by an array). - * The caller is thus free to modify the returned array. - * - *

This method acts as bridge between array-based and collection-based - * APIs. - * - * @return an array containing all the elements in this set - */ - public Object[] toArray() { - return al.toArray(); - } - - /** - * Returns an array containing all of the elements in this set; the - * runtime type of the returned array is that of the specified array. - * If the set fits in the specified array, it is returned therein. - * Otherwise, a new array is allocated with the runtime type of the - * specified array and the size of this set. - * - *

If this set fits in the specified array with room to spare - * (i.e., the array has more elements than this set), the element in - * the array immediately following the end of the set is set to - * null. (This is useful in determining the length of this - * set only if the caller knows that this set does not contain - * any null elements.) - * - *

If this set makes any guarantees as to what order its elements - * are returned by its iterator, this method must return the elements - * in the same order. - * - *

Like the {@link #toArray()} method, this method acts as bridge between - * array-based and collection-based APIs. Further, this method allows - * precise control over the runtime type of the output array, and may, - * under certain circumstances, be used to save allocation costs. - * - *

Suppose x is a set known to contain only strings. - * The following code can be used to dump the set into a newly allocated - * array of String: - * - *

-     *     String[] y = x.toArray(new String[0]);
- * - * Note that toArray(new Object[0]) is identical in function to - * toArray(). - * - * @param a the array into which the elements of this set are to be - * stored, if it is big enough; otherwise, a new array of the same - * runtime type is allocated for this purpose. - * @return an array containing all the elements in this set - * @throws ArrayStoreException if the runtime type of the specified array - * is not a supertype of the runtime type of every element in this - * set - * @throws NullPointerException if the specified array is null - */ - public T[] toArray(T[] a) { - return al.toArray(a); - } - - /** - * Removes all of the elements from this set. - * The set will be empty after this call returns. - */ - public void clear() { - al.clear(); + super(c); +// al = new CopyOnWriteArrayList(); +// al.addAllAbsent(c); } - /** - * Removes the specified element from this set if it is present. - * More formally, removes an element e such that - * (o==null ? e==null : o.equals(e)), - * if this set contains such an element. Returns true if - * this set contained the element (or equivalently, if this set - * changed as a result of the call). (This set will not contain the - * element once the call returns.) - * - * @param o object to be removed from this set, if present - * @return true if this set contained the specified element - */ - public boolean remove(Object o) { - return al.remove(o); - } - - /** - * Adds the specified element to this set if it is not already present. - * More formally, adds the specified element e to this set if - * the set contains no element e2 such that - * (e==null ? e2==null : e.equals(e2)). - * If this set already contains the element, the call leaves the set - * unchanged and returns false. - * - * @param e element to be added to this set - * @return true if this set did not already contain the specified - * element - */ - public boolean add(E e) { - return al.addIfAbsent(e); - } - - /** - * Returns true if this set contains all of the elements of the - * specified collection. If the specified collection is also a set, this - * method returns true if it is a subset of this set. - * - * @param c collection to be checked for containment in this set - * @return true if this set contains all of the elements of the - * specified collection - * @throws NullPointerException if the specified collection is null - * @see #contains(Object) - */ - public boolean containsAll(Collection c) { - return al.containsAll(c); - } - - /** - * Adds all of the elements in the specified collection to this set if - * they're not already present. If the specified collection is also a - * set, the addAll operation effectively modifies this set so - * that its value is the union of the two sets. The behavior of - * this operation is undefined if the specified collection is modified - * while the operation is in progress. - * - * @param c collection containing elements to be added to this set - * @return true if this set changed as a result of the call - * @throws NullPointerException if the specified collection is null - * @see #add(Object) - */ - public boolean addAll(Collection c) { - return al.addAllAbsent(c) > 0; - } - - /** - * Removes from this set all of its elements that are contained in the - * specified collection. If the specified collection is also a set, - * this operation effectively modifies this set so that its value is the - * asymmetric set difference of the two sets. - * - * @param c collection containing elements to be removed from this set - * @return true if this set changed as a result of the call - * @throws ClassCastException if the class of an element of this set - * is incompatible with the specified collection (optional) - * @throws NullPointerException if this set contains a null element and the - * specified collection does not permit null elements (optional), - * or if the specified collection is null - * @see #remove(Object) - */ - public boolean removeAll(Collection c) { - return al.removeAll(c); - } - - /** - * Retains only the elements in this set that are contained in the - * specified collection. In other words, removes from this set all of - * its elements that are not contained in the specified collection. If - * the specified collection is also a set, this operation effectively - * modifies this set so that its value is the intersection of the - * two sets. - * - * @param c collection containing elements to be retained in this set - * @return true if this set changed as a result of the call - * @throws ClassCastException if the class of an element of this set - * is incompatible with the specified collection (optional) - * @throws NullPointerException if this set contains a null element and the - * specified collection does not permit null elements (optional), - * or if the specified collection is null - * @see #remove(Object) - */ - public boolean retainAll(Collection c) { - return al.retainAll(c); - } - - /** - * Returns an iterator over the elements contained in this set - * in the order in which these elements were added. - * - *

The returned iterator provides a snapshot of the state of the set - * when the iterator was constructed. No synchronization is needed while - * traversing the iterator. The iterator does NOT support the - * remove method. - * - * @return an iterator over the elements in this set - */ - public Iterator iterator() { - return al.iterator(); - } - - /** - * Compares the specified object with this set for equality. - * Returns {@code true} if the specified object is the same object - * as this object, or if it is also a {@link Set} and the elements - * returned by an {@linkplain List#iterator() iterator} over the - * specified set are the same as the elements returned by an - * iterator over this set. More formally, the two iterators are - * considered to return the same elements if they return the same - * number of elements and for every element {@code e1} returned by - * the iterator over the specified set, there is an element - * {@code e2} returned by the iterator over this set such that - * {@code (e1==null ? e2==null : e1.equals(e2))}. - * - * @param o object to be compared for equality with this set - * @return {@code true} if the specified object is equal to this set - */ - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof Set)) - return false; - Set set = (Set)(o); - Iterator it = set.iterator(); - - // Uses O(n^2) algorithm that is only appropriate - // for small sets, which CopyOnWriteArraySets should be. - - // Use a single snapshot of underlying array - Object[] elements = al.getArray(); - int len = elements.length; - // Mark matched elements to avoid re-checking - boolean[] matched = new boolean[len]; - int k = 0; - outer: while (it.hasNext()) { - if (++k > len) - return false; - Object x = it.next(); - for (int i = 0; i < len; ++i) { - if (!matched[i] && eq(x, elements[i])) { - matched[i] = true; - continue outer; - } - } - return false; - } - return k == len; - } - - /** - * Test for equality, coping with nulls. - */ - private static boolean eq(Object o1, Object o2) { - return (o1 == null ? o2 == null : o1.equals(o2)); - } +// /** +// * Returns the number of elements in this set. +// * +// * @return the number of elements in this set +// */ +// public int size() { +// return al.size(); +// } +// +// /** +// * Returns true if this set contains no elements. +// * +// * @return true if this set contains no elements +// */ +// public boolean isEmpty() { +// return al.isEmpty(); +// } +// +// /** +// * Returns true if this set contains the specified element. +// * More formally, returns true if and only if this set +// * contains an element e such that +// * (o==null ? e==null : o.equals(e)). +// * +// * @param o element whose presence in this set is to be tested +// * @return true if this set contains the specified element +// */ +// public boolean contains(Object o) { +// return al.contains(o); +// } +// +// /** +// * Returns an array containing all of the elements in this set. +// * If this set makes any guarantees as to what order its elements +// * are returned by its iterator, this method must return the +// * elements in the same order. +// * +// *

The returned array will be "safe" in that no references to it +// * are maintained by this set. (In other words, this method must +// * allocate a new array even if this set is backed by an array). +// * The caller is thus free to modify the returned array. +// * +// *

This method acts as bridge between array-based and collection-based +// * APIs. +// * +// * @return an array containing all the elements in this set +// */ +// public Object[] toArray() { +// return al.toArray(); +// } +// +// /** +// * Returns an array containing all of the elements in this set; the +// * runtime type of the returned array is that of the specified array. +// * If the set fits in the specified array, it is returned therein. +// * Otherwise, a new array is allocated with the runtime type of the +// * specified array and the size of this set. +// * +// *

If this set fits in the specified array with room to spare +// * (i.e., the array has more elements than this set), the element in +// * the array immediately following the end of the set is set to +// * null. (This is useful in determining the length of this +// * set only if the caller knows that this set does not contain +// * any null elements.) +// * +// *

If this set makes any guarantees as to what order its elements +// * are returned by its iterator, this method must return the elements +// * in the same order. +// * +// *

Like the {@link #toArray()} method, this method acts as bridge between +// * array-based and collection-based APIs. Further, this method allows +// * precise control over the runtime type of the output array, and may, +// * under certain circumstances, be used to save allocation costs. +// * +// *

Suppose x is a set known to contain only strings. +// * The following code can be used to dump the set into a newly allocated +// * array of String: +// * +// *

+//     *     String[] y = x.toArray(new String[0]);
+// * +// * Note that toArray(new Object[0]) is identical in function to +// * toArray(). +// * +// * @param a the array into which the elements of this set are to be +// * stored, if it is big enough; otherwise, a new array of the same +// * runtime type is allocated for this purpose. +// * @return an array containing all the elements in this set +// * @throws ArrayStoreException if the runtime type of the specified array +// * is not a supertype of the runtime type of every element in this +// * set +// * @throws NullPointerException if the specified array is null +// */ +// public T[] toArray(T[] a) { +// return al.toArray(a); +// } +// +// /** +// * Removes all of the elements from this set. +// * The set will be empty after this call returns. +// */ +// public void clear() { +// al.clear(); +// } +// +// /** +// * Removes the specified element from this set if it is present. +// * More formally, removes an element e such that +// * (o==null ? e==null : o.equals(e)), +// * if this set contains such an element. Returns true if +// * this set contained the element (or equivalently, if this set +// * changed as a result of the call). (This set will not contain the +// * element once the call returns.) +// * +// * @param o object to be removed from this set, if present +// * @return true if this set contained the specified element +// */ +// public boolean remove(Object o) { +// return al.remove(o); +// } +// +// /** +// * Adds the specified element to this set if it is not already present. +// * More formally, adds the specified element e to this set if +// * the set contains no element e2 such that +// * (e==null ? e2==null : e.equals(e2)). +// * If this set already contains the element, the call leaves the set +// * unchanged and returns false. +// * +// * @param e element to be added to this set +// * @return true if this set did not already contain the specified +// * element +// */ +// public boolean add(E e) { +// return al.addIfAbsent(e); +// } +// +// /** +// * Returns true if this set contains all of the elements of the +// * specified collection. If the specified collection is also a set, this +// * method returns true if it is a subset of this set. +// * +// * @param c collection to be checked for containment in this set +// * @return true if this set contains all of the elements of the +// * specified collection +// * @throws NullPointerException if the specified collection is null +// * @see #contains(Object) +// */ +// public boolean containsAll(Collection c) { +// return al.containsAll(c); +// } +// +// /** +// * Adds all of the elements in the specified collection to this set if +// * they're not already present. If the specified collection is also a +// * set, the addAll operation effectively modifies this set so +// * that its value is the union of the two sets. The behavior of +// * this operation is undefined if the specified collection is modified +// * while the operation is in progress. +// * +// * @param c collection containing elements to be added to this set +// * @return true if this set changed as a result of the call +// * @throws NullPointerException if the specified collection is null +// * @see #add(Object) +// */ +// public boolean addAll(Collection c) { +// return al.addAllAbsent(c) > 0; +// } +// +// /** +// * Removes from this set all of its elements that are contained in the +// * specified collection. If the specified collection is also a set, +// * this operation effectively modifies this set so that its value is the +// * asymmetric set difference of the two sets. +// * +// * @param c collection containing elements to be removed from this set +// * @return true if this set changed as a result of the call +// * @throws ClassCastException if the class of an element of this set +// * is incompatible with the specified collection (optional) +// * @throws NullPointerException if this set contains a null element and the +// * specified collection does not permit null elements (optional), +// * or if the specified collection is null +// * @see #remove(Object) +// */ +// public boolean removeAll(Collection c) { +// return al.removeAll(c); +// } +// +// /** +// * Retains only the elements in this set that are contained in the +// * specified collection. In other words, removes from this set all of +// * its elements that are not contained in the specified collection. If +// * the specified collection is also a set, this operation effectively +// * modifies this set so that its value is the intersection of the +// * two sets. +// * +// * @param c collection containing elements to be retained in this set +// * @return true if this set changed as a result of the call +// * @throws ClassCastException if the class of an element of this set +// * is incompatible with the specified collection (optional) +// * @throws NullPointerException if this set contains a null element and the +// * specified collection does not permit null elements (optional), +// * or if the specified collection is null +// * @see #remove(Object) +// */ +// public boolean retainAll(Collection c) { +// return al.retainAll(c); +// } +// +// /** +// * Returns an iterator over the elements contained in this set +// * in the order in which these elements were added. +// * +// *

The returned iterator provides a snapshot of the state of the set +// * when the iterator was constructed. No synchronization is needed while +// * traversing the iterator. The iterator does NOT support the +// * remove method. +// * +// * @return an iterator over the elements in this set +// */ +// public Iterator iterator() { +// return al.iterator(); +// } +// +// /** +// * Compares the specified object with this set for equality. +// * Returns {@code true} if the specified object is the same object +// * as this object, or if it is also a {@link Set} and the elements +// * returned by an {@linkplain List#iterator() iterator} over the +// * specified set are the same as the elements returned by an +// * iterator over this set. More formally, the two iterators are +// * considered to return the same elements if they return the same +// * number of elements and for every element {@code e1} returned by +// * the iterator over the specified set, there is an element +// * {@code e2} returned by the iterator over this set such that +// * {@code (e1==null ? e2==null : e1.equals(e2))}. +// * +// * @param o object to be compared for equality with this set +// * @return {@code true} if the specified object is equal to this set +// */ +// public boolean equals(Object o) { +// if (o == this) +// return true; +// if (!(o instanceof Set)) +// return false; +// Set set = (Set)(o); +// Iterator it = set.iterator(); +// +// // Uses O(n^2) algorithm that is only appropriate +// // for small sets, which CopyOnWriteArraySets should be. +// +// // Use a single snapshot of underlying array +// Object[] elements = al.getArray(); +// int len = elements.length; +// // Mark matched elements to avoid re-checking +// boolean[] matched = new boolean[len]; +// int k = 0; +// outer: while (it.hasNext()) { +// if (++k > len) +// return false; +// Object x = it.next(); +// for (int i = 0; i < len; ++i) { +// if (!matched[i] && eq(x, elements[i])) { +// matched[i] = true; +// continue outer; +// } +// } +// return false; +// } +// return k == len; +// } +// +// /** +// * Test for equality, coping with nulls. +// */ +// private static boolean eq(Object o1, Object o2) { +// return (o1 == null ? o2 == null : o1.equals(o2)); +// } } diff --git a/sources/net.sf.j2s.java.core/src/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java b/sources/net.sf.j2s.java.core/src/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java index 4408ff3b1..4841f1115 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java +++ b/sources/net.sf.j2s.java.core/src/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java @@ -310,24 +310,26 @@ private static final class AtomicReferenceFieldUpdaterImpl final Class fieldClass; final int modifiers; try { - field = AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public Field run() throws NoSuchFieldException { - return tclass.getDeclaredField(fieldName); - } - }); - modifiers = field.getModifiers(); - sun.reflect.misc.ReflectUtil.ensureMemberAccess( - caller, tclass, null, modifiers); - ClassLoader cl = tclass.getClassLoader(); - ClassLoader ccl = caller.getClassLoader(); - if ((ccl != null) && (ccl != cl) && - ((cl == null) || !isAncestor(cl, ccl))) { - sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass); - } + field = +// AccessController.doPrivileged( +// new PrivilegedExceptionAction() { +// public Field run() throws NoSuchFieldException { +// return + tclass.getDeclaredField(fieldName); +// } +// }); +// modifiers = field.getModifiers(); +// sun.reflect.misc.ReflectUtil.ensureMemberAccess( +// caller, tclass, null, modifiers); +// ClassLoader cl = tclass.getClassLoader(); +// ClassLoader ccl = caller.getClassLoader(); +// if ((ccl != null) && (ccl != cl) && +// ((cl == null) || !isAncestor(cl, ccl))) { +// sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass); +// } fieldClass = field.getType(); - } catch (PrivilegedActionException pae) { - throw new RuntimeException(pae.getException()); +// } catch (PrivilegedActionException pae) { +// throw new RuntimeException(pae.getException()); } catch (Exception ex) { throw new RuntimeException(ex); } @@ -337,11 +339,12 @@ public Field run() throws NoSuchFieldException { if (vclass.isPrimitive()) throw new IllegalArgumentException("Must be reference type"); - if (!Modifier.isVolatile(modifiers)) - throw new IllegalArgumentException("Must be volatile type"); +// if (!Modifier.isVolatile(modifiers)) +// throw new IllegalArgumentException("Must be volatile type"); - this.cclass = (Modifier.isProtected(modifiers) && - caller != tclass) ? caller : null; + this.cclass = null; +// (Modifier.isProtected(modifiers) && +// caller != tclass) ? caller : null; this.tclass = tclass; if (vclass == Object.class) this.vclass = null; @@ -350,21 +353,21 @@ public Field run() throws NoSuchFieldException { offset = unsafe.objectFieldOffset(field); } - /** - * Returns true if the second classloader can be found in the first - * classloader's delegation chain. - * Equivalent to the inaccessible: first.isAncestor(second). - */ - private static boolean isAncestor(ClassLoader first, ClassLoader second) { - ClassLoader acl = first; - do { - acl = acl.getParent(); - if (second == acl) { - return true; - } - } while (acl != null); - return false; - } +// /** +// * Returns true if the second classloader can be found in the first +// * classloader's delegation chain. +// * Equivalent to the inaccessible: first.isAncestor(second). +// */ +// private static boolean isAncestor(ClassLoader first, ClassLoader second) { +// ClassLoader acl = first; +// do { +// acl = acl.getParent(); +// if (second == acl) { +// return true; +// } +// } while (acl != null); +// return false; +// } void targetCheck(T obj) { if (!tclass.isInstance(obj)) @@ -381,7 +384,8 @@ void updateCheck(T obj, V update) { ensureProtectedAccess(obj); } - public boolean compareAndSet(T obj, V expect, V update) { + @Override + public boolean compareAndSet(T obj, V expect, V update) { if (obj == null || obj.getClass() != tclass || cclass != null || (update != null && vclass != null && vclass != update.getClass())) @@ -389,7 +393,8 @@ public boolean compareAndSet(T obj, V expect, V update) { return unsafe.compareAndSwapObject(obj, offset, expect, update); } - public boolean weakCompareAndSet(T obj, V expect, V update) { + @Override + public boolean weakCompareAndSet(T obj, V expect, V update) { // same implementation as strong form for now if (obj == null || obj.getClass() != tclass || cclass != null || (update != null && vclass != null && @@ -398,7 +403,8 @@ public boolean weakCompareAndSet(T obj, V expect, V update) { return unsafe.compareAndSwapObject(obj, offset, expect, update); } - public void set(T obj, V newValue) { + @Override + public void set(T obj, V newValue) { if (obj == null || obj.getClass() != tclass || cclass != null || (newValue != null && vclass != null && vclass != newValue.getClass())) @@ -406,7 +412,8 @@ public void set(T obj, V newValue) { unsafe.putObjectVolatile(obj, offset, newValue); } - public void lazySet(T obj, V newValue) { + @Override + public void lazySet(T obj, V newValue) { if (obj == null || obj.getClass() != tclass || cclass != null || (newValue != null && vclass != null && vclass != newValue.getClass())) @@ -414,14 +421,16 @@ public void lazySet(T obj, V newValue) { unsafe.putOrderedObject(obj, offset, newValue); } - @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public V get(T obj) { if (obj == null || obj.getClass() != tclass || cclass != null) targetCheck(obj); return (V)unsafe.getObjectVolatile(obj, offset); } - @SuppressWarnings("unchecked") + @Override + @SuppressWarnings("unchecked") public V getAndSet(T obj, V newValue) { if (obj == null || obj.getClass() != tclass || cclass != null || (newValue != null && vclass != null && diff --git a/sources/net.sf.j2s.java.core/src/java/util/prefs/FileSystemPreferences.java b/sources/net.sf.j2s.java.core/src/java/util/prefs/FileSystemPreferences.java index 71408e08c..221423537 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/prefs/FileSystemPreferences.java +++ b/sources/net.sf.j2s.java.core/src/java/util/prefs/FileSystemPreferences.java @@ -402,17 +402,17 @@ private FileSystemPreferences(FileSystemPreferences parent, String name) { super(parent, name); isUserNode = parent.isUserNode; dir = new File(parent.dir, dirName(name)); - newNode = !dir.exists(); - -// prefsFile = new File(dir, "prefs.xml"); -// tmpFile = new File(dir, "prefs.tmp"); - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Void run() { - newNode = !dir.exists(); - return null; - } - }); + newNode = false;// formerly, File.exists was true;//!dir.exists(); +// +//// prefsFile = new File(dir, "prefs.xml"); +//// tmpFile = new File(dir, "prefs.tmp"); +// AccessController.doPrivileged(new PrivilegedAction() { +// @Override +// public Void run() { +// newNode = !dir.exists(); +// return null; +// } +// }); if (newNode) { // These 2 things guarantee node will get wrtten at next flush/sync prefsCache = new TreeMap<>(); diff --git a/sources/net.sf.j2s.java.core/src/java/util/prefs/Preferences.java b/sources/net.sf.j2s.java.core/src/java/util/prefs/Preferences.java index b84e2be58..1ab22c343 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/prefs/Preferences.java +++ b/sources/net.sf.j2s.java.core/src/java/util/prefs/Preferences.java @@ -270,23 +270,24 @@ public PreferencesFactory run() { } private static PreferencesFactory factory1() { - // 2. Try service provider interface - Iterator itr = ServiceLoader - .load(PreferencesFactory.class, ClassLoader.getSystemClassLoader()) - .iterator(); - - // choose first provider instance - while (itr.hasNext()) { - try { - return itr.next(); - } catch (ServiceConfigurationError sce) { - if (sce.getCause() instanceof SecurityException) { - // Ignore the security exception, try the next provider - continue; - } - throw sce; - } - } + // SwingJS not +// // 2. Try service provider interface +// Iterator itr = ServiceLoader +// .load(PreferencesFactory.class, ClassLoader.getSystemClassLoader()) +// .iterator(); +// +// // choose first provider instance +// while (itr.hasNext()) { +// try { +// return itr.next(); +// } catch (ServiceConfigurationError sce) { +// if (sce.getCause() instanceof SecurityException) { +// // Ignore the security exception, try the next provider +// continue; +// } +// throw sce; +// } +// } // 3. Use platform-specific system-wide default String osName = System.getProperty("os.name"); diff --git a/sources/net.sf.j2s.java.core/src/java/util/regex/Pattern.js b/sources/net.sf.j2s.java.core/src/java/util/regex/Pattern.js index fb0fa722e..18b040c52 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/regex/Pattern.js +++ b/sources/net.sf.j2s.java.core/src/java/util/regex/Pattern.js @@ -86,14 +86,13 @@ Clazz.newMeth(C$,"matches$S$CharSequence",function(regex,input){ }, 1); Clazz.newMeth(C$,"quote$S",function(s){ - var sb=new StringBuffer().append("\\Q"); - var apos=0; - var k; - while((k=s.indexOf("\\E",apos))>=0){ - sb.append$S(s.substring(apos,k+2)).append("\\\\E\\Q"); - apos=k+2; + for (var i = s.length; --i >= 0;) { + var ch = s[i]; + if ("[]^$|()\\+*?{}=!".indexOf(ch) >= 0) + s = s.substring(0,i) + "\\" + s.substring(i); } - return sb.append$S(s.substring(apos)).append("\\E").toString(); + return s; + // \\Q \\E does not work in JavaScript }, 1); })(); diff --git a/sources/net.sf.j2s.java.core/src/java/util/zip/Deflater.java b/sources/net.sf.j2s.java.core/src/java/util/zip/Deflater.java index 778932a82..c90cbe173 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/zip/Deflater.java +++ b/sources/net.sf.j2s.java.core/src/java/util/zip/Deflater.java @@ -29,13 +29,478 @@ package java.util.zip; public class Deflater extends swingjs.jzlib.Deflater { - - public static final int DEFAULT_COMPRESSION = -1; - - public Deflater(int compressionLevel) { - super(); - if (compressionLevel != Integer.MAX_VALUE) - init(compressionLevel, 0, false); - } + + /** + * Compression method for the deflate algorithm (the only one currently + * supported). + */ + public static final int DEFLATED = 8; + + /** + * Compression level for no compression. + */ + public static final int NO_COMPRESSION = 0; + + /** + * Compression level for fastest compression. + */ + public static final int BEST_SPEED = 1; + + /** + * Compression level for best compression. + */ + public static final int BEST_COMPRESSION = 9; + + /** + * Default compression level. + */ + public static final int DEFAULT_COMPRESSION = -1; + + /** + * Compression strategy best used for data consisting mostly of small values + * with a somewhat random distribution. Forces more Huffman coding and less + * string matching. + */ + public static final int FILTERED = 1; + + /** + * Compression strategy for Huffman coding only. + */ + public static final int HUFFMAN_ONLY = 2; + + /** + * Default compression strategy. + */ + public static final int DEFAULT_STRATEGY = 0; + + /** + * Compression flush mode used to achieve best compression result. + * + * @see Deflater#deflate(byte[], int, int, int) + * @since 1.7 + */ + public static final int NO_FLUSH = 0; + + /** + * Compression flush mode used to flush out all pending output; may degrade + * compression for some compression algorithms. + * + * @see Deflater#deflate(byte[], int, int, int) + * @since 1.7 + */ + public static final int SYNC_FLUSH = 2; + + /** + * Compression flush mode used to flush out all pending output and reset the + * deflater. Using this mode too often can seriously degrade compression. + * + * @see Deflater#deflate(byte[], int, int, int) + * @since 1.7 + */ + public static final int FULL_FLUSH = 3; + + private int level; + + private int strategy; + + private boolean setParams; + + private DeflaterOutputStream out; + + private int maxBits; + + private boolean nowrap; + + public Deflater(int level, boolean nowrap) { + super(); + this.level = level; + this.strategy = DEFAULT_STRATEGY; + if (level != Integer.MAX_VALUE) + init(level, DEFAULT_STRATEGY, 0, nowrap); + } + + /** + * Creates a new compressor using the specified compression level. Compressed + * data will be generated in ZLIB format. + * + * @param level the compression level (0-9) + */ + public Deflater(int level) { + this(level, false); + } + + /** + * Creates a new compressor with the default compression level. Compressed data + * will be generated in ZLIB format. + */ + public Deflater() { + this(DEFAULT_COMPRESSION, false); + } + + private void init(int level, int strategy, int maxBits, boolean nowrap) { + super.init(level, maxBits, nowrap); + this.strategy = strategy; + this.maxBits = maxBits; + this.nowrap = nowrap; + } + + /** + * Sets input data for compression. This should be called whenever needsInput() + * returns true indicating that more input data is required. + * + * @param b the input data bytes + * @param off the start offset of the data + * @param len the length of the data + * @see Deflater#needsInput + */ + public void setInput(byte[] b, int off, int len) { + if (b== null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || off > b.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + super.setInput(b, off, len, false); + } + /** + * Sets input data for compression. This should be called whenever needsInput() + * returns true indicating that more input data is required. + * + * @param b the input data bytes + * @see Deflater#needsInput + */ + public void setInput(byte[] b) { + setInput(b, 0, b.length); + } + + /** + * Sets preset dictionary for compression. A preset dictionary is used when the + * history buffer can be predetermined. When the data is later uncompressed with + * Inflater.inflate(), Inflater.getAdler() can be called in order to get the + * Adler-32 value of the dictionary required for decompression. + * + * @param b the dictionary data bytes + * @param off the start offset of the data + * @param len the length of the data + * @see Inflater#inflate + * @see Inflater#getAdler + */ + public void setDictionary(byte[] b, int off, int len) { + if (b == null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || off > b.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + super.setDictionaryRet(b, off, len); + } + + /** + * Sets preset dictionary for compression. A preset dictionary is used when the + * history buffer can be predetermined. When the data is later uncompressed with + * Inflater.inflate(), Inflater.getAdler() can be called in order to get the + * Adler-32 value of the dictionary required for decompression. + * + * @param b the dictionary data bytes + * @see Inflater#inflate + * @see Inflater#getAdler + */ + public void setDictionary(byte[] b) { + setDictionary(b, 0, b.length); + } + + /** + * Sets the compression strategy to the specified value. + * + *

+ * If the compression strategy is changed, the next invocation of + * {@code deflate} will compress the input available so far with the old + * strategy (and may be flushed); the new strategy will take effect only after + * that invocation. + * + * @param strategy the new compression strategy + * @exception IllegalArgumentException if the compression strategy is invalid + */ + public void setStrategy(int strategy) { + switch (strategy) { + case DEFAULT_STRATEGY: + case FILTERED: + case HUFFMAN_ONLY: + break; + default: + throw new IllegalArgumentException(); + } + if (this.strategy != strategy) { + this.strategy = strategy; + setParams = true; + } + } + + /** + * Sets the compression level to the specified value. + * + *

+ * If the compression level is changed, the next invocation of {@code deflate} + * will compress the input available so far with the old level (and may be + * flushed); the new level will take effect only after that invocation. + * + * @param level the new compression level (0-9) + * @exception IllegalArgumentException if the compression level is invalid + */ + public void setLevel(int level) { + if ((level < 0 || level > 9) && level != DEFAULT_COMPRESSION) { + throw new IllegalArgumentException("invalid compression level"); + } + if (this.level != level) { + this.level = level; + setParams = true; + } + } + + /** + * Returns true if the input data buffer is empty and setInput() should be + * called in order to provide more input. + * + * @return true if the input data buffer is empty and setInput() should be + * called in order to provide more input + */ + @Override + public boolean needsInput() { + return super.needsInput(); + } + + /** + * When called, indicates that compression should end with the current contents + * of the input buffer. + */ + @Override + public void finish() { + super.finish(); + } + + /** + * Returns true if the end of the compressed data output stream has been + * reached. + * + * @return true if the end of the compressed data output stream has been reached + */ + @Override + public boolean finished() { + return super.finished(); + } + + /** + * Compresses the input data and fills specified buffer with compressed data. + * Returns actual number of bytes of compressed data. A return value of 0 + * indicates that {@link #needsInput() needsInput} should be called in order to + * determine if more input data is required. + * + *

+ * This method uses {@link #NO_FLUSH} as its compression flush mode. An + * invocation of this method of the form {@code deflater.deflate(b, off, len)} + * yields the same result as the invocation of + * {@code deflater.deflate(b, off, len, Deflater.NO_FLUSH)}. + * + * @param b the buffer for the compressed data + * @param off the start offset of the data + * @param len the maximum number of bytes of compressed data + * @return the actual number of bytes of compressed data written to the output + * buffer + */ + public int deflate(byte[] out, int off, int len) { + return deflate(out, off, len, NO_FLUSH); + } + + /** + * Compresses the input data and fills specified buffer with compressed data. + * Returns actual number of bytes of compressed data. A return value of 0 + * indicates that {@link #needsInput() needsInput} should be called in order to + * determine if more input data is required. + * + *

+ * This method uses {@link #NO_FLUSH} as its compression flush mode. An + * invocation of this method of the form {@code deflater.deflate(b)} yields the + * same result as the invocation of + * {@code deflater.deflate(b, 0, b.length, Deflater.NO_FLUSH)}. + * + * @param b the buffer for the compressed data + * @return the actual number of bytes of compressed data written to the output + * buffer + */ + public int deflate(byte[] b) { + return deflate(b, 0, b.length, NO_FLUSH); + } + + /** + * Compresses the input data and fills the specified buffer with compressed + * data. Returns actual number of bytes of data compressed. + * + *

+ * Compression flush mode is one of the following three modes: + * + *

    + *
  • {@link #NO_FLUSH}: allows the deflater to decide how much data to + * accumulate, before producing output, in order to achieve the best compression + * (should be used in normal use scenario). A return value of 0 in this flush + * mode indicates that {@link #needsInput()} should be called in order to + * determine if more input data is required. + * + *
  • {@link #SYNC_FLUSH}: all pending output in the deflater is flushed, to + * the specified output buffer, so that an inflater that works on compressed + * data can get all input data available so far (In particular the + * {@link #needsInput()} returns {@code true} after this invocation if enough + * output space is provided). Flushing with {@link #SYNC_FLUSH} may degrade + * compression for some compression algorithms and so it should be used only + * when necessary. + * + *
  • {@link #FULL_FLUSH}: all pending output is flushed out as with + * {@link #SYNC_FLUSH}. The compression state is reset so that the inflater that + * works on the compressed output data can restart from this point if previous + * compressed data has been damaged or if random access is desired. Using + * {@link #FULL_FLUSH} too often can seriously degrade compression. + *
+ * + *

+ * In the case of {@link #FULL_FLUSH} or {@link #SYNC_FLUSH}, if the return + * value is {@code len}, the space available in output buffer {@code b}, this + * method should be invoked again with the same {@code flush} parameter and more + * output space. + * + * @param b the buffer for the compressed data + * @param off the start offset of the data + * @param len the maximum number of bytes of compressed data + * @param flush the compression flush mode + * @return the actual number of bytes of compressed data written to the output + * buffer + * + * @throws IllegalArgumentException if the flush mode is invalid + * @since 1.7 + */ + @Override + public int deflate(byte[] b, int off, int len, int flush) { + if (b == null) { + throw new NullPointerException(); + } + if (off < 0 || len < 0 || off > b.length - len) { + throw new ArrayIndexOutOfBoundsException(); + } + return super.deflate(b, off, len, flush); +// synchronized (zsRef) { +// ensureOpen(); +// if (flush == NO_FLUSH || flush == SYNC_FLUSH || flush == FULL_FLUSH) { +// int thisLen = this.len; +// int n = deflateBytes(zsRef.address(), b, off, len, flush); +// bytesWritten += n; +// bytesRead += (thisLen - this.len); +// return n; +// } +// throw new IllegalArgumentException(); +// } + } + + /** + * Returns the ADLER-32 value of the uncompressed data. + * + * @return the ADLER-32 value of the uncompressed data + */ + public int getAdler() { + return (int) super.getAdlerL(); + } + + /** + * Returns the total number of uncompressed bytes input so far. + * + *

+ * Since the number of bytes may be greater than Integer.MAX_VALUE, the + * {@link #getBytesRead()} method is now the preferred means of obtaining this + * information. + *

+ * + * @return the total number of uncompressed bytes input so far + */ + public int getTotalIn() { + return (int) getBytesRead(); + } + + /** + * Returns the total number of uncompressed bytes input so far. + * + * @return the total (non-negative) number of uncompressed bytes input so far + * @since 1.5 + */ + @Override + public long getBytesRead() { + return super.getBytesRead(); +// synchronized (zsRef) { +// ensureOpen(); +// return bytesRead; +// } + } + + /** + * Returns the total number of compressed bytes output so far. + * + *

+ * Since the number of bytes may be greater than Integer.MAX_VALUE, the + * {@link #getBytesWritten()} method is now the preferred means of obtaining + * this information. + *

+ * + * @return the total number of compressed bytes output so far + */ + public int getTotalOut() { + return (int) getBytesWritten(); + } + + /** + * Returns the total number of compressed bytes output so far. + * + * @return the total (non-negative) number of compressed bytes output so far + * @since 1.5 + */ + @Override + public long getBytesWritten() { + return super.getBytesWritten(); +// synchronized (zsRef) { +// ensureOpen(); +// return bytesWritten; +// } + } + + /** + * Resets deflater so that a new set of input data can be processed. Keeps + * current compression level and strategy settings. + */ + @Override + public void reset() { + super.reset(); + } + + /** + * Closes the compressor and discards any unprocessed input. This method should + * be called when the compressor is no longer being used, but will also be + * called automatically by the finalize() method. Once this method is called, + * the behavior of the Deflater object is undefined. + */ + @Override + public void end() { + super.end(); +// synchronized (zsRef) { +// long addr = zsRef.address(); +// zsRef.clear(); +// if (addr != 0) { +// end(addr); +// buf = null; +// } +// } + } + + /** + * Closes the compressor when garbage is collected. + */ + @Override + protected void finalize() { + end(); + } } diff --git a/sources/net.sf.j2s.java.core/src/java/util/zip/InflaterInputStream.java b/sources/net.sf.j2s.java.core/src/java/util/zip/InflaterInputStream.java index 3ebafe8c2..7b92f10ce 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/zip/InflaterInputStream.java +++ b/sources/net.sf.j2s.java.core/src/java/util/zip/InflaterInputStream.java @@ -30,14 +30,23 @@ import java.io.InputStream; -class InflaterInputStream extends swingjs.jzlib.InflaterInputStream { +public class InflaterInputStream extends swingjs.jzlib.InflaterInputStream { protected Inflater inf; - InflaterInputStream(InputStream in, Inflater inflater, int size) { + public InflaterInputStream(InputStream in, Inflater inflater, int size) { super(in, inflater, size, true); this.inf = inflater; } -// + + public InflaterInputStream(InputStream in, Inflater inflater) { + this(in, inflater, 512); + } + + public InflaterInputStream(InputStream in) { + this(in, new Inflater(), 512); + } + + // /** // * Returns the total number of bytes remaining in the input buffer. // * This can be used to find out what bytes still remain in the input diff --git a/sources/net.sf.j2s.java.core/src/java/util/zip/ZipInputStream.java b/sources/net.sf.j2s.java.core/src/java/util/zip/ZipInputStream.java index 6608987ca..aa2f63887 100644 --- a/sources/net.sf.j2s.java.core/src/java/util/zip/ZipInputStream.java +++ b/sources/net.sf.j2s.java.core/src/java/util/zip/ZipInputStream.java @@ -431,8 +431,8 @@ private void readEnd(ZipEntry e) throws IOException { } if ((flag & 8) == 8) { /* "Data Descriptor" present */ - if (inf.getTotalOut() > ZipConstants64.ZIP64_MAGICVAL - || inf.getTotalIn() > ZipConstants64.ZIP64_MAGICVAL) { + if (inf.getTotalOutL() > ZipConstants64.ZIP64_MAGICVAL + || inf.getTotalInL() > ZipConstants64.ZIP64_MAGICVAL) { // ZIP64 format readFully(tmpbuf, 0, ZipConstants64.ZIP64_EXTHDR); long sig = get32(tmpbuf, 0); @@ -465,13 +465,13 @@ private void readEnd(ZipEntry e) throws IOException { } } } - if (e.size != inf.getTotalOut()) { + if (e.size != inf.getTotalOutL()) { throw new ZipException("invalid entry size (expected " + e.size - + " but got " + inf.getTotalOut() + " bytes)"); + + " but got " + inf.getTotalOutL() + " bytes)"); } - if (e.csize != inf.getTotalIn()) { + if (e.csize != inf.getTotalInL()) { throw new ZipException("invalid entry compressed size (expected " - + e.csize + " but got " + inf.getTotalIn() + " bytes)"); + + e.csize + " but got " + inf.getTotalInL() + " bytes)"); } if (e.crc != crc.getValue()) { throw new ZipException("invalid entry CRC (expected 0x" diff --git a/sources/net.sf.j2s.java.core/src/javajs/async/AsyncColorChooser.java b/sources/net.sf.j2s.java.core/src/javajs/async/AsyncColorChooser.java index 4eb57913a..2474833ae 100644 --- a/sources/net.sf.j2s.java.core/src/javajs/async/AsyncColorChooser.java +++ b/sources/net.sf.j2s.java.core/src/javajs/async/AsyncColorChooser.java @@ -48,7 +48,7 @@ public void propertyChange(PropertyChangeEvent evt) { private void setListener(ActionListener a) { listener = a; - /** @j2sNative Clazz.loadClass("javax.swing.JColorChooser");javax.swing.JColorChooser.listener = this */ + /** @j2sNative Clazz.load("javax.swing.JColorChooser");javax.swing.JColorChooser.listener = this */ } private void unsetListener() { diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/IIOParamController.java b/sources/net.sf.j2s.java.core/src/javax/imageio/IIOParamController.java new file mode 100644 index 000000000..7b285acd5 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/IIOParamController.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2000, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio; + +/** + * An interface to be implemented by objects that can determine the + * settings of an IIOParam object, either by putting up a + * GUI to obtain values from a user, or by other means. This + * interface merely specifies a generic activate method + * that invokes the controller, without regard for how the controller + * obtains values (i.e., whether the controller puts up a GUI + * or merely computes a set of values is irrelevant to this + * interface). + * + *

Within the activate method, a controller obtains + * initial values by querying the IIOParam object's + * get methods, modifies values by whatever means, then + * invokes the IIOParam object's set methods + * to modify the appropriate settings. Normally, these + * set methods will be invoked all at once at a final + * commit in order that a cancel operation not disturb existing + * values. In general, applications may expect that when the + * activate method returns true, the + * IIOParam object is ready for use in a read or write + * operation. + * + *

Vendors may choose to provide GUIs for the + * IIOParam subclasses they define for a particular + * plug-in. These can be set up as default controllers in the + * corresponding IIOParam subclasses. + * + *

Applications may override any default GUIs and provide their + * own controllers embedded in their own framework. All that is + * required is that theactivate method behave modally + * (not returning until either cancelled or committed), though it need + * not put up an explicitly modal dialog. Such a non-modal GUI + * component would be coded roughly as follows: + * + *
+ *

+ * class MyGUI extends SomeComponent implements IIOParamController {
+ *
+ *    public MyGUI() {
+ *        // ...
+ *        setEnabled(false);
+ *    }
+ *
+ *    public boolean activate(IIOParam param) {
+ *        // disable other components if desired
+ *        setEnabled(true);
+ *        // go to sleep until either cancelled or committed
+ *        boolean ret = false;
+ *        if (!cancelled) {
+ *            // set values on param
+ *            ret = true;
+ *        }
+ *        setEnabled(false);
+ *        // enable any components disabled above
+ *        return ret;
+ *    }
+ * 
+ * + *

Alternatively, an algorithmic process such as a database lookup + * or the parsing of a command line could be used as a controller, in + * which case the activate method would simply look up or + * compute the settings, call the IIOParam.setXXX + * methods, and return true. + * + * @see IIOParam#setController + * @see IIOParam#getController + * @see IIOParam#getDefaultController + * @see IIOParam#hasController + * @see IIOParam#activateController + * + */ +public interface IIOParamController { + + /** + * Activates the controller. If true is returned, + * all settings in the IIOParam object should be + * ready for use in a read or write operation. If + * false is returned, no settings in the + * IIOParam object will be disturbed (i.e., + * the user canceled the operation). + * + * @param param the IIOParam object to be modified. + * + * @return true if the IIOParam has been + * modified, false otherwise. + * + * @exception IllegalArgumentException if param is + * null or is not an instance of the correct class. + */ + boolean activate(IIOParam param); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/ImageIO.java b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageIO.java index 7984fe959..ab1325734 100644 --- a/sources/net.sf.j2s.java.core/src/javax/imageio/ImageIO.java +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageIO.java @@ -33,16 +33,31 @@ import java.awt.image.RenderedImage; import java.io.BufferedInputStream; import java.io.File; +import java.io.FilePermission; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Method; import java.net.URL; +import java.security.AccessController; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.spi.ImageOutputStreamSpi; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.spi.ImageReaderWriterSpi; +import javax.imageio.spi.ImageTranscoderSpi; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.spi.ServiceRegistry; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; +import sun.awt.AppContext; +import sun.security.action.GetPropertyAction; //import javax.imageio.spi.IIORegistry; //import javax.imageio.spi.ImageReaderSpi; //import javax.imageio.spi.ImageReaderWriterSpi; @@ -54,6 +69,7 @@ //import sun.security.action.GetPropertyAction; import swingjs.JSUtil; import swingjs.image.GifImageWriter; +import swingjs.image.JSImageWriter; import swingjs.image.JpgImageWriter; import swingjs.image.PngImageWriter; @@ -65,7 +81,7 @@ */ public final class ImageIO { - // private static final IIORegistry theRegistry = + // private static final IIORegistry getRegistry() = // IIORegistry.getDefaultInstance(); // // /** @@ -110,1300 +126,68 @@ public final class ImageIO { // * @see ClassLoader#getResources // */ // public static void scanForPlugins() { - // theRegistry.registerApplicationClasspathSpis(); + // getRegistry().registerApplicationClasspathSpis(); // } // ImageInputStreams - /** - * A class to hold information about caching. Each ThreadGroup - * will have its own copy via the AppContext mechanism. - */ - static class CacheInfo { - boolean useCache = false; // SwingJS - // File cacheDirectory = null; - Boolean hasPermission = Boolean.TRUE;// null; - - public CacheInfo() { - } - - public boolean getUseCache() { - return useCache; - } - - public void setUseCache(boolean useCache) { - this.useCache = useCache; - } - - public File getCacheDirectory() { - return null;// return cacheDirectory; - } - - public void setCacheDirectory(File cacheDirectory) { - JSUtil.notImplemented(null); - // this.cacheDirectory = cacheDirectory; - } - - public Boolean getHasPermission() { - return hasPermission; - } - - public void setHasPermission(Boolean hasPermission) { - this.hasPermission = hasPermission; - } - } - - // /** - // * Returns the CacheInfo object associated with this - // * ThreadGroup. - // */ - // private static synchronized CacheInfo getCacheInfo() { - // AppContext context = AppContext.getAppContext(); - // CacheInfo info = (CacheInfo)context.get(CacheInfo.class); - // if (info == null) { - // info = new CacheInfo(); - // context.put(CacheInfo.class, info); - // } - // return info; - // } - // - // /** - // * Returns the default temporary (cache) directory as defined by the - // * java.io.tmpdir system property. - // */ - // private static String getTempDir() { - // GetPropertyAction a = new GetPropertyAction("java.io.tmpdir"); - // return (String)AccessController.doPrivileged(a); - // } - // - // /** - // * Determines whether the caller has write access to the cache - // * directory, stores the result in the CacheInfo object, - // * and returns the decision. This method helps to prevent mysterious - // * SecurityExceptions to be thrown when this convenience class is used - // * in an applet, for example. - // */ - // private static boolean hasCachePermission() { - // Boolean hasPermission = getCacheInfo().getHasPermission(); - // - // if (hasPermission != null) { - // return hasPermission.booleanValue(); - // } else { - // try { - // SecurityManager security = System.getSecurityManager(); - // if (security != null) { - // File cachedir = getCacheDirectory(); - // String cachepath; - // - // if (cachedir != null) { - // cachepath = cachedir.getPath(); - // } else { - // cachepath = getTempDir(); - // - // if (cachepath == null) { - // getCacheInfo().setHasPermission(Boolean.FALSE); - // return false; - // } - // } - // - // security.checkWrite(cachepath); - // } - // } catch (SecurityException e) { - // getCacheInfo().setHasPermission(Boolean.FALSE); - // return false; - // } - // - // getCacheInfo().setHasPermission(Boolean.TRUE); - // return true; - // } - // } - // - /** - * Sets a flag indicating whether a disk-based cache file should be used when - * creating ImageInputStreams and ImageOutputStream - * s. - * - *

- * When reading from a standard InputStream>, it may be necessary - * to save previously read information in a cache since the underlying stream - * does not allow data to be re-read. Similarly, when writing to a standard - * OutputStream, a cache may be used to allow a previously - * written value to be changed before flushing it to the final destination. - * - *

- * The cache may reside in main memory or on disk. Setting this flag to - * false disallows the use of disk for future streams, which may - * be advantageous when working with small images, as the overhead of creating - * and destroying files is removed. - * - *

- * On startup, the value is set to true. - * - * @param useCache - * a boolean indicating whether a cache file should be - * used, in cases where it is optional. - * - * @see #getUseCache - */ - public static void setUseCache(boolean useCache) { - // getCacheInfo().setUseCache(useCache); - } - - /** - * Returns the current value set by setUseCache, or - * true if no explicit setting has been made. - * - * @return true if a disk-based cache may be used for - * ImageInputStreams and ImageOutputStreams. - * - * @see #setUseCache - */ - public static boolean getUseCache() { - return false; - // return getCacheInfo().getUseCache(); - } - - /** - * Sets the directory where cache files are to be created. A value of - * null indicates that the system-dependent default - * temporary-file directory is to be used. If getUseCache returns - * false, this value is ignored. - * - * @param cacheDirectory - * a File specifying a directory. - * - * @see File#createTempFile(String, String, File) - * - * @exception SecurityException - * if the security manager denies access to the directory. - * @exception IllegalArgumentException - * if cacheDir is non-null but is not a - * directory. - * - * @see #getCacheDirectory - */ - public static void setCacheDirectory(File cacheDirectory) { - // if ((cacheDirectory != null) && !(cacheDirectory.isDirectory())) { - // throw new IllegalArgumentException("Not a directory!"); - // } - // getCacheInfo().setCacheDirectory(cacheDirectory); - // getCacheInfo().setHasPermission(null); - } - - /** - * Returns the current value set by setCacheDirectory, or - * null if no explicit setting has been made. - * - * @return a File indicating the directory where cache files will - * be created, or null to indicate the system-dependent - * default temporary-file directory. - * - * @see #setCacheDirectory - */ - public static File getCacheDirectory() { - return null; - // return getCacheInfo().getCacheDirectory(); - } - - // /** - // * Returns an ImageInputStream that will take its - // * input from the given Object. The set of - // * ImageInputStreamSpis registered with the - // * IIORegistry class is queried and the first one - // * that is able to take input from the supplied object is used to - // * create the returned ImageInputStream. If no - // * suitable ImageInputStreamSpi exists, - // * null is returned. - // * - // *

The current cache settings from getUseCacheand - // * getCacheDirectory will be used to control caching. - // * - // * @param input an Object to be used as an input - // * source, such as a File, readable - // * RandomAccessFile, or InputStream. - // * - // * @return an ImageInputStream, or null. - // * - // * @exception IllegalArgumentException if input - // * is null. - // * @exception IOException if a cache file is needed but cannot be - // * created. - // * - // * @see javax.imageio.spi.ImageInputStreamSpi - // */ - // public static ImageInputStream createImageInputStream(Object input) - // throws IOException { - // if (input == null) { - // throw new IllegalArgumentException("input == null!"); - // } - // - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageInputStreamSpi.class, - // true); - // } catch (IllegalArgumentException e) { - // return null; - // } - // - // boolean usecache = getUseCache() && hasCachePermission(); - // - // while (iter.hasNext()) { - // ImageInputStreamSpi spi = (ImageInputStreamSpi)iter.next(); - // if (spi.getInputClass().isInstance(input)) { - // try { - // return spi.createInputStreamInstance(input, - // usecache, - // getCacheDirectory()); - // } catch (IOException e) { - // throw new IIOException("Can't create cache file!", e); - // } - // } - // } - // - // return null; - // } - - // SwingJS TODO // ImageOutputStreams - // - // /** - // * Returns an ImageOutputStream that will send its - // * output to the given Object. The set of - // * ImageOutputStreamSpis registered with the - // * IIORegistry class is queried and the first one - // * that is able to send output from the supplied object is used to - // * create the returned ImageOutputStream. If no - // * suitable ImageOutputStreamSpi exists, - // * null is returned. - // * - // *

The current cache settings from getUseCacheand - // * getCacheDirectory will be used to control caching. - // * - // * @param output an Object to be used as an output - // * destination, such as a File, writable - // * RandomAccessFile, or OutputStream. - // * - // * @return an ImageOutputStream, or - // * null. - // * - // * @exception IllegalArgumentException if output is - // * null. - // * @exception IOException if a cache file is needed but cannot be - // * created. - // * - // * @see javax.imageio.spi.ImageOutputStreamSpi - // */ - // public static ImageOutputStream createImageOutputStream(Object output) - // throws IOException { - // if (output == null) { - // throw new IllegalArgumentException("output == null!"); - // } - // - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageOutputStreamSpi.class, - // true); - // } catch (IllegalArgumentException e) { - // return null; - // } - // - // boolean usecache = getUseCache() && hasCachePermission(); - // - // while (iter.hasNext()) { - // ImageOutputStreamSpi spi = (ImageOutputStreamSpi)iter.next(); - // if (spi.getOutputClass().isInstance(output)) { - // try { - // return spi.createOutputStreamInstance(output, - // usecache, - // getCacheDirectory()); - // } catch (IOException e) { - // throw new IIOException("Can't create cache file!", e); - // } - // } - // } - // - // return null; - // } - - // private static enum SpiInfo { - // FORMAT_NAMES { - // @Override - // String[] info(ImageReaderWriterSpi spi) { - // return spi.getFormatNames(); - // } - // }, - // MIME_TYPES { - // @Override - // String[] info(ImageReaderWriterSpi spi) { - // return spi.getMIMETypes(); - // } - // }, - // FILE_SUFFIXES { - // @Override - // String[] info(ImageReaderWriterSpi spi) { - // return spi.getFileSuffixes(); - // } - // }; - // - // abstract String[] info(ImageReaderWriterSpi spi); - // } - // - // private static - // String[] getReaderWriterInfo(Class spiClass, SpiInfo spiInfo) - // { - // // Ensure category is present - // Iterator iter; - // try { - // iter = theRegistry.getServiceProviders(spiClass, true); - // } catch (IllegalArgumentException e) { - // return new String[0]; - // } - // - // HashSet s = new HashSet(); - // while (iter.hasNext()) { - // ImageReaderWriterSpi spi = iter.next(); - // Collections.addAll(s, spiInfo.info(spi)); - // } - // - // return s.toArray(new String[s.size()]); - // } - // - // Readers - - private static int PNG = 0; - private static int JPG = 1; - private static int GIF = 2; - private static int BMP = 3; - - private static String[] readerTypes = { "image/png", "image/jpeg", - "image/x-png", "image/vnd.wap.wbmp", "image/gif", "image/bmp" }; - @SuppressWarnings("unused") - private static int[] readerMap = { PNG, JPG, PNG, BMP, GIF, BMP }; - private static String[] readerFormatNames = { "jpg", "BMP", "bmp", "JPG", - "wbmp", "jpeg", "png", "PNG", "JPEG", "WBMP", "GIF", "gif" }; - private static String[] readerSuffixes = { "jpg", "bmp", "jpeg", "wbmp", - "png", "gif" }; - - /** - * Returns an array of Strings listing all of the informal format - * names understood by the current set of registered readers. - * - * @return an array of Strings. - */ - public static String[] getReaderFormatNames() { - return readerFormatNames; - // return getReaderWriterInfo(ImageReaderSpi.class, - // SpiInfo.FORMAT_NAMES); - } - - /** - * Returns an array of Strings listing all of the MIME types - * understood by the current set of registered readers. - * - * @return an array of Strings. - */ - public static String[] getReaderMIMETypes() { - return readerTypes; - // return getReaderWriterInfo(ImageReaderSpi.class, - // SpiInfo.MIME_TYPES); - } - - /** - * Returns an array of Strings listing all of the file suffixes - * associated with the formats understood by the current set of registered - * readers. - * - * @return an array of Strings. - * @since 1.6 - */ - public static String[] getReaderFileSuffixes() { - return readerSuffixes; - // return getReaderWriterInfo(ImageReaderSpi.class, - // SpiInfo.FILE_SUFFIXES); - } - - // static class ImageReaderIterator implements Iterator { - // // Contains ImageReaderSpis - // public Iterator iter; - // - // public ImageReaderIterator(Iterator iter) { - // this.iter = iter; - // } - // - // public boolean hasNext() { - // return iter.hasNext(); - // } - // - // public ImageReader next() { - // ImageReaderSpi spi = null; - // try { - // spi = (ImageReaderSpi)iter.next(); - // return spi.createReaderInstance(); - // } catch (IOException e) { - // // Deregister the spi in this case, but only as - // // an ImageReaderSpi - // theRegistry.deregisterServiceProvider(spi, ImageReaderSpi.class); - // } - // return null; - // } - // - // public void remove() { - // throw new UnsupportedOperationException(); - // } - // } - // - // static class CanDecodeInputFilter - // implements ServiceRegistry.Filter { - // - // Object input; - // - // public CanDecodeInputFilter(Object input) { - // this.input = input; - // } - // - // public boolean filter(Object elt) { - // try { - // ImageReaderSpi spi = (ImageReaderSpi)elt; - // ImageInputStream stream = null; - // if (input instanceof ImageInputStream) { - // stream = (ImageInputStream)input; - // } - // - // // Perform mark/reset as a defensive measure - // // even though plug-ins are supposed to take - // // care of it. - // boolean canDecode = false; - // if (stream != null) { - // stream.mark(); - // } - // canDecode = spi.canDecodeInput(input); - // if (stream != null) { - // stream.reset(); - // } - // - // return canDecode; - // } catch (IOException e) { - // return false; - // } - // } - // } - // - // static class CanEncodeImageAndFormatFilter - // implements ServiceRegistry.Filter { - // - // ImageTypeSpecifier type; - // String formatName; - // - // public CanEncodeImageAndFormatFilter(ImageTypeSpecifier type, - // String formatName) { - // this.type = type; - // this.formatName = formatName; - // } - // - // public boolean filter(Object elt) { - // ImageWriterSpi spi = (ImageWriterSpi)elt; - // return Arrays.asList(spi.getFormatNames()).contains(formatName) && - // spi.canEncodeImage(type); - // } - // } - // - // static class ContainsFilter - // implements ServiceRegistry.Filter { - // - // Method method; - // String name; - // - // // method returns an array of Strings - // public ContainsFilter(Method method, - // String name) { - // this.method = method; - // this.name = name; - // } - // - // public boolean filter(Object elt) { - // try { - // return contains((String[])method.invoke(elt), name); - // } catch (Exception e) { - // return false; - // } - // } - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageReaders that claim to be able to - // * decode the supplied Object, typically an - // * ImageInputStream. - // * - // *

The stream position is left at its prior position upon - // * exit from this method. - // * - // * @param input an ImageInputStream or other - // * Object containing encoded image data. - // * - // * @return an Iterator containing ImageReaders. - // * - // * @exception IllegalArgumentException if input is - // * null. - // * - // * @see javax.imageio.spi.ImageReaderSpi#canDecodeInput - // */ - // public static Iterator getImageReaders(Object input) { - // if (input == null) { - // throw new IllegalArgumentException("input == null!"); - // } - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageReaderSpi.class, - // new CanDecodeInputFilter(input), - // true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // - // return new ImageReaderIterator(iter); - // } - // - // private static Method readerFormatNamesMethod; - // private static Method readerFileSuffixesMethod; - // private static Method readerMIMETypesMethod; - // private static Method writerFormatNamesMethod; - // private static Method writerFileSuffixesMethod; - // private static Method writerMIMETypesMethod; - // - // static { - // try { - // readerFormatNamesMethod = - // ImageReaderSpi.class.getMethod("getFormatNames"); - // readerFileSuffixesMethod = - // ImageReaderSpi.class.getMethod("getFileSuffixes"); - // readerMIMETypesMethod = - // ImageReaderSpi.class.getMethod("getMIMETypes"); - // - // writerFormatNamesMethod = - // ImageWriterSpi.class.getMethod("getFormatNames"); - // writerFileSuffixesMethod = - // ImageWriterSpi.class.getMethod("getFileSuffixes"); - // writerMIMETypesMethod = - // ImageWriterSpi.class.getMethod("getMIMETypes"); - // } catch (NoSuchMethodException e) { - // e.printStackTrace(); - // } - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageReaders that claim to be able to - // * decode the named format. - // * - // * @param formatName a String containing the informal - // * name of a format (e.g., "jpeg" or "tiff". - // * - // * @return an Iterator containing - // * ImageReaders. - // * - // * @exception IllegalArgumentException if formatName - // * is null. - // * - // * @see javax.imageio.spi.ImageReaderSpi#getFormatNames - // */ - // public static Iterator - // getImageReadersByFormatName(String formatName) - // { - // if (formatName == null) { - // throw new IllegalArgumentException("formatName == null!"); - // } - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageReaderSpi.class, - // new ContainsFilter(readerFormatNamesMethod, - // formatName), - // true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // return new ImageReaderIterator(iter); - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageReaders that claim to be able to - // * decode files with the given suffix. - // * - // * @param fileSuffix a String containing a file - // * suffix (e.g., "jpg" or "tiff"). - // * - // * @return an Iterator containing - // * ImageReaders. - // * - // * @exception IllegalArgumentException if fileSuffix - // * is null. - // * - // * @see javax.imageio.spi.ImageReaderSpi#getFileSuffixes - // */ - // public static Iterator - // getImageReadersBySuffix(String fileSuffix) - // { - // if (fileSuffix == null) { - // throw new IllegalArgumentException("fileSuffix == null!"); - // } - // // Ensure category is present - // Iterator iter; - // try { - // iter = theRegistry.getServiceProviders(ImageReaderSpi.class, - // new ContainsFilter(readerFileSuffixesMethod, - // fileSuffix), - // true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // return new ImageReaderIterator(iter); - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageReaders that claim to be able to - // * decode files with the given MIME type. - // * - // * @param MIMEType a String containing a file - // * suffix (e.g., "image/jpeg" or "image/x-bmp"). - // * - // * @return an Iterator containing - // * ImageReaders. - // * - // * @exception IllegalArgumentException if MIMEType is - // * null. - // * - // * @see javax.imageio.spi.ImageReaderSpi#getMIMETypes - // */ - // public static Iterator - // getImageReadersByMIMEType(String MIMEType) - // { - // if (MIMEType == null) { - // throw new IllegalArgumentException("MIMEType == null!"); - // } - // // Ensure category is present - // Iterator iter; - // try { - // iter = theRegistry.getServiceProviders(ImageReaderSpi.class, - // new ContainsFilter(readerMIMETypesMethod, - // MIMEType), - // true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // return new ImageReaderIterator(iter); - // } - // - // // Writers - // - // /** - // * Returns an array of Strings listing all of the - // * informal format names understood by the current set of registered - // * writers. - // * - // * @return an array of Strings. - // */ - // public static String[] getWriterFormatNames() { - // return getReaderWriterInfo(ImageWriterSpi.class, - // SpiInfo.FORMAT_NAMES); - // } - // - // /** - // * Returns an array of Strings listing all of the - // * MIME types understood by the current set of registered - // * writers. - // * - // * @return an array of Strings. - // */ - // public static String[] getWriterMIMETypes() { - // return getReaderWriterInfo(ImageWriterSpi.class, - // SpiInfo.MIME_TYPES); - // } - // - // /** - // * Returns an array of Strings listing all of the - // * file suffixes associated with the formats understood - // * by the current set of registered writers. - // * - // * @return an array of Strings. - // * @since 1.6 - // */ - // public static String[] getWriterFileSuffixes() { - // return getReaderWriterInfo(ImageWriterSpi.class, - // SpiInfo.FILE_SUFFIXES); - // } - // - // static class ImageWriterIterator implements Iterator { - // // Contains ImageWriterSpis - // public Iterator iter; - // - // public ImageWriterIterator(Iterator iter) { - // this.iter = iter; - // } - // - // public boolean hasNext() { - // return iter.hasNext(); - // } - // - // public ImageWriter next() { - // ImageWriterSpi spi = null; - // try { - // spi = (ImageWriterSpi)iter.next(); - // return spi.createWriterInstance(); - // } catch (IOException e) { - // // Deregister the spi in this case, but only as a writerSpi - // theRegistry.deregisterServiceProvider(spi, ImageWriterSpi.class); - // } - // return null; - // } - // - // public void remove() { - // throw new UnsupportedOperationException(); - // } - // } - // - // private static boolean contains(String[] names, String name) { - // for (int i = 0; i < names.length; i++) { - // if (name.equalsIgnoreCase(names[i])) { - // return true; - // } - // } - // - // return false; - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageWriters that claim to be able to - // * encode the named format. - // * - // * @param formatName a String containing the informal - // * name of a format (e.g., "jpeg" or "tiff". - // * - // * @return an Iterator containing - // * ImageWriters. - // * - // * @exception IllegalArgumentException if formatName is - // * null. - // * - // * @see javax.imageio.spi.ImageWriterSpi#getFormatNames - // */ - // public static Iterator - // getImageWritersByFormatName(String formatName) - // { - // if (formatName == null) { - // throw new IllegalArgumentException("formatName == null!"); - // } - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageWriterSpi.class, - // new ContainsFilter(writerFormatNamesMethod, - // formatName), - // true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // return new ImageWriterIterator(iter); - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageWriters that claim to be able to - // * encode files with the given suffix. - // * - // * @param fileSuffix a String containing a file - // * suffix (e.g., "jpg" or "tiff"). - // * - // * @return an Iterator containing ImageWriters. - // * - // * @exception IllegalArgumentException if fileSuffix is - // * null. - // * - // * @see javax.imageio.spi.ImageWriterSpi#getFileSuffixes - // */ - // public static Iterator - // getImageWritersBySuffix(String fileSuffix) - // { - // if (fileSuffix == null) { - // throw new IllegalArgumentException("fileSuffix == null!"); - // } - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageWriterSpi.class, - // new ContainsFilter(writerFileSuffixesMethod, - // fileSuffix), - // true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // return new ImageWriterIterator(iter); - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageWriters that claim to be able to - // * encode files with the given MIME type. - // * - // * @param MIMEType a String containing a file - // * suffix (e.g., "image/jpeg" or "image/x-bmp"). - // * - // * @return an Iterator containing ImageWriters. - // * - // * @exception IllegalArgumentException if MIMEType is - // * null. - // * - // * @see javax.imageio.spi.ImageWriterSpi#getMIMETypes - // */ - // public static Iterator - // getImageWritersByMIMEType(String MIMEType) - // { - // if (MIMEType == null) { - // throw new IllegalArgumentException("MIMEType == null!"); - // } - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageWriterSpi.class, - // new ContainsFilter(writerMIMETypesMethod, - // MIMEType), - // true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // return new ImageWriterIterator(iter); - // } - // - // /** - // * Returns an ImageWritercorresponding to the given - // * ImageReader, if there is one, or null - // * if the plug-in for this ImageReader does not - // * specify a corresponding ImageWriter, or if the - // * given ImageReader is not registered. This - // * mechanism may be used to obtain an ImageWriter - // * that will understand the internal structure of non-pixel - // * metadata (as encoded by IIOMetadata objects) - // * generated by the ImageReader. By obtaining this - // * data from the ImageReader and passing it on to the - // * ImageWriter obtained with this method, a client - // * program can read an image, modify it in some way, and write it - // * back out preserving all metadata, without having to understand - // * anything about the structure of the metadata, or even about - // * the image format. Note that this method returns the - // * "preferred" writer, which is the first in the list returned by - // * javax.imageio.spi.ImageReaderSpi.getImageWriterSpiNames(). - // * - // * @param reader an instance of a registered ImageReader. - // * - // * @return an ImageWriter, or null. - // * - // * @exception IllegalArgumentException if reader is - // * null. - // * - // * @see #getImageReader(ImageWriter) - // * @see javax.imageio.spi.ImageReaderSpi#getImageWriterSpiNames() - // */ - // public static ImageWriter getImageWriter(ImageReader reader) { - // if (reader == null) { - // throw new IllegalArgumentException("reader == null!"); - // } - // - // ImageReaderSpi readerSpi = reader.getOriginatingProvider(); - // if (readerSpi == null) { - // Iterator readerSpiIter; - // // Ensure category is present - // try { - // readerSpiIter = - // theRegistry.getServiceProviders(ImageReaderSpi.class, - // false); - // } catch (IllegalArgumentException e) { - // return null; - // } - // - // while (readerSpiIter.hasNext()) { - // ImageReaderSpi temp = (ImageReaderSpi) readerSpiIter.next(); - // if (temp.isOwnReader(reader)) { - // readerSpi = temp; - // break; - // } - // } - // if (readerSpi == null) { - // return null; - // } - // } - // - // String[] writerNames = readerSpi.getImageWriterSpiNames(); - // if (writerNames == null) { - // return null; - // } - // - // Class writerSpiClass = null; - // try { - // writerSpiClass = Class.forName(writerNames[0], true, - // ClassLoader.getSystemClassLoader()); - // } catch (ClassNotFoundException e) { - // return null; - // } - // - // ImageWriterSpi writerSpi = (ImageWriterSpi) - // theRegistry.getServiceProviderByClass(writerSpiClass); - // if (writerSpi == null) { - // return null; - // } - // - // try { - // return writerSpi.createWriterInstance(); - // } catch (IOException e) { - // // Deregister the spi in this case, but only as a writerSpi - // theRegistry.deregisterServiceProvider(writerSpi, - // ImageWriterSpi.class); - // return null; - // } - // } - // - // /** - // * Returns an ImageReadercorresponding to the given - // * ImageWriter, if there is one, or null - // * if the plug-in for this ImageWriter does not - // * specify a corresponding ImageReader, or if the - // * given ImageWriter is not registered. This method - // * is provided principally for symmetry with - // * getImageWriter(ImageReader). Note that this - // * method returns the "preferred" reader, which is the first in - // * the list returned by - // * javax.imageio.spi.ImageWriterSpi.getImageReaderSpiNames(). - // * - // * @param writer an instance of a registered ImageWriter. - // * - // * @return an ImageReader, or null. - // * - // * @exception IllegalArgumentException if writer is - // * null. - // * - // * @see #getImageWriter(ImageReader) - // * @see javax.imageio.spi.ImageWriterSpi#getImageReaderSpiNames() - // */ - // public static ImageReader getImageReader(ImageWriter writer) { - // if (writer == null) { - // throw new IllegalArgumentException("writer == null!"); - // } - // - // ImageWriterSpi writerSpi = writer.getOriginatingProvider(); - // if (writerSpi == null) { - // Iterator writerSpiIter; - // // Ensure category is present - // try { - // writerSpiIter = - // theRegistry.getServiceProviders(ImageWriterSpi.class, - // false); - // } catch (IllegalArgumentException e) { - // return null; - // } - // - // while (writerSpiIter.hasNext()) { - // ImageWriterSpi temp = (ImageWriterSpi) writerSpiIter.next(); - // if (temp.isOwnWriter(writer)) { - // writerSpi = temp; - // break; - // } - // } - // if (writerSpi == null) { - // return null; - // } - // } - // - // String[] readerNames = writerSpi.getImageReaderSpiNames(); - // if (readerNames == null) { - // return null; - // } - // - // Class readerSpiClass = null; - // try { - // readerSpiClass = Class.forName(readerNames[0], true, - // ClassLoader.getSystemClassLoader()); - // } catch (ClassNotFoundException e) { - // return null; - // } - // - // ImageReaderSpi readerSpi = (ImageReaderSpi) - // theRegistry.getServiceProviderByClass(readerSpiClass); - // if (readerSpi == null) { - // return null; - // } - // - // try { - // return readerSpi.createReaderInstance(); - // } catch (IOException e) { - // // Deregister the spi in this case, but only as a readerSpi - // theRegistry.deregisterServiceProvider(readerSpi, - // ImageReaderSpi.class); - // return null; - // } - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageWriters that claim to be able to - // * encode images of the given layout (specified using an - // * ImageTypeSpecifier) in the given format. - // * - // * @param type an ImageTypeSpecifier indicating the - // * layout of the image to be written. - // * @param formatName the informal name of the format. - // * - // * @return an Iterator containing ImageWriters. - // * - // * @exception IllegalArgumentException if any parameter is - // * null. - // * - // * @see javax.imageio.spi.ImageWriterSpi#canEncodeImage(ImageTypeSpecifier) - // */ - // public static Iterator - // getImageWriters(ImageTypeSpecifier type, String formatName) - // { - // if (type == null) { - // throw new IllegalArgumentException("type == null!"); - // } - // if (formatName == null) { - // throw new IllegalArgumentException("formatName == null!"); - // } - // - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageWriterSpi.class, - // new CanEncodeImageAndFormatFilter(type, - // formatName), - // true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // - // return new ImageWriterIterator(iter); - // } - // - // static class ImageTranscoderIterator - // implements Iterator - // { - // // Contains ImageTranscoderSpis - // public Iterator iter; - // - // public ImageTranscoderIterator(Iterator iter) { - // this.iter = iter; - // } - // - // public boolean hasNext() { - // return iter.hasNext(); - // } - // - // public ImageTranscoder next() { - // ImageTranscoderSpi spi = null; - // spi = (ImageTranscoderSpi)iter.next(); - // return spi.createTranscoderInstance(); - // } - // - // public void remove() { - // throw new UnsupportedOperationException(); - // } - // } - // - // static class TranscoderFilter - // implements ServiceRegistry.Filter { - // - // String readerSpiName; - // String writerSpiName; - // - // public TranscoderFilter(ImageReaderSpi readerSpi, - // ImageWriterSpi writerSpi) { - // this.readerSpiName = readerSpi.getClass().getName(); - // this.writerSpiName = writerSpi.getClass().getName(); - // } - // - // public boolean filter(Object elt) { - // ImageTranscoderSpi spi = (ImageTranscoderSpi)elt; - // String readerName = spi.getReaderServiceProviderName(); - // String writerName = spi.getWriterServiceProviderName(); - // return (readerName.equals(readerSpiName) && - // writerName.equals(writerSpiName)); - // } - // } - // - // /** - // * Returns an Iterator containing all currently - // * registered ImageTranscoders that claim to be - // * able to transcode between the metadata of the given - // * ImageReader and ImageWriter. - // * - // * @param reader an ImageReader. - // * @param writer an ImageWriter. - // * - // * @return an Iterator containing - // * ImageTranscoders. - // * - // * @exception IllegalArgumentException if reader or - // * writer is null. - // */ - // public static Iterator - // getImageTranscoders(ImageReader reader, ImageWriter writer) - // { - // if (reader == null) { - // throw new IllegalArgumentException("reader == null!"); - // } - // if (writer == null) { - // throw new IllegalArgumentException("writer == null!"); - // } - // ImageReaderSpi readerSpi = reader.getOriginatingProvider(); - // ImageWriterSpi writerSpi = writer.getOriginatingProvider(); - // ServiceRegistry.Filter filter = - // new TranscoderFilter(readerSpi, writerSpi); - // - // Iterator iter; - // // Ensure category is present - // try { - // iter = theRegistry.getServiceProviders(ImageTranscoderSpi.class, - // filter, true); - // } catch (IllegalArgumentException e) { - // return Collections.emptyList().iterator(); - // } - // return new ImageTranscoderIterator(iter); - // } - // - // All-in-one methods + // Readers + + private static int PNG = 0; + private static int JPG = 1; + private static int GIF = 2; + private static int BMP = 3; + + private static String[] readerTypes = { "image/png", "image/jpeg", + "image/x-png", "image/vnd.wap.wbmp", "image/gif", "image/bmp" }; + @SuppressWarnings("unused") + private static int[] readerMap = { PNG, JPG, PNG, BMP, GIF, BMP }; + private static String[] readerFormatNames = { "jpg", "BMP", "bmp", "JPG", + "wbmp", "jpeg", "png", "PNG", "JPEG", "WBMP", "GIF", "gif" }; + private static String[] readerSuffixes = { "jpg", "bmp", "jpeg", "wbmp", + "png", "gif" }; + + private static boolean useSPI; - // /** - // * Returns a BufferedImage as the result of decoding - // * a supplied File with an ImageReader - // * chosen automatically from among those currently registered. - // * The File is wrapped in an - // * ImageInputStream. If no registered - // * ImageReader claims to be able to read the - // * resulting stream, null is returned. - // * - // *

The current cache settings from getUseCacheand - // * getCacheDirectory will be used to control caching in the - // * ImageInputStream that is created. - // * - // *

Note that there is no read method that takes a - // * filename as a String; use this method instead after - // * creating a File from the filename. - // * - // *

This method does not attempt to locate - // * ImageReaders that can read directly from a - // * File; that may be accomplished using - // * IIORegistry and ImageReaderSpi. - // * - // * @param input a File to read from. - // * - // * @return a BufferedImage containing the decoded - // * contents of the input, or null. - // * - // * @exception IllegalArgumentException if input is - // * null. - // * @exception IOException if an error occurs during reading. - // */ - // public static BufferedImage read(File input) throws IOException { - // if (input == null) { - // throw new IllegalArgumentException("input == null!"); - // } - // if (!input.canRead()) { - // throw new IIOException("Can't read input file!"); - // } - // - // ImageInputStream stream = createImageInputStream(input); - // if (stream == null) { - // throw new IIOException("Can't create an ImageInputStream!"); - // } - // BufferedImage bi = read(stream); - // if (bi == null) { - // stream.close(); - // } - // return bi; - // } - // /** - * Returns a BufferedImage as the result of decoding a supplied - * InputStream with an ImageReader chosen - * automatically from among those currently registered. The - * InputStream is wrapped in an ImageInputStream. If - * no registered ImageReader claims to be able to read the - * resulting stream, null is returned. - * - *

- * The current cache settings from getUseCacheand - * getCacheDirectory will be used to control caching in the - * ImageInputStream that is created. - * - *

- * This method does not attempt to locate ImageReaders that can - * read directly from an InputStream; that may be accomplished - * using IIORegistry and ImageReaderSpi. - * - *

- * This method does not close the provided InputStream - * after the read operation has completed; it is the responsibility of the - * caller to close the stream, if desired. + * Returns an array of Strings listing all of the informal format + * names understood by the current set of registered readers. * - * @param input - * an InputStream to read from. + * @return an array of Strings. + */ + public static String[] getReaderFormatNames() { + return (useSPI ? getReaderWriterInfo(ImageReaderSpi.class, SpiInfo.FORMAT_NAMES) : readerFormatNames); + } + + /** + * Returns an array of Strings listing all of the MIME types + * understood by the current set of registered readers. * - * @return a BufferedImage containing the decoded contents of the - * input, or null. + * @return an array of Strings. + */ + public static String[] getReaderMIMETypes() { + return useSPI ? getReaderWriterInfo(ImageReaderSpi.class, SpiInfo.MIME_TYPES) : readerTypes; + } + + /** + * Returns an array of Strings listing all of the file suffixes + * associated with the formats understood by the current set of registered + * readers. * - * @exception IllegalArgumentException - * if input is null. - * @exception IOException - * if an error occurs during reading. + * @return an array of Strings. + * @since 1.6 */ - public static BufferedImage read(InputStream input) throws IOException { - return (BufferedImage) Toolkit - .getDefaultToolkit() - .createImage( - JSUtil - .getSignedStreamBytes(input instanceof BufferedInputStream ? (BufferedInputStream) input - : new BufferedInputStream(input))); + public static String[] getReaderFileSuffixes() { + return useSPI ? getReaderWriterInfo(ImageReaderSpi.class, SpiInfo.FILE_SUFFIXES) : readerSuffixes; } + // private static ImageInputStream createImageInputStream(InputStream input) { // return null; // // } - /** - * Returns a BufferedImage as the result of decoding a supplied - * URL with an ImageReader chosen automatically from - * among those currently registered. An InputStream is obtained - * from the URL, which is wrapped in an - * ImageInputStream. If no registered ImageReader - * claims to be able to read the resulting stream, null is - * returned. - * - *

- * The current cache settings from getUseCacheand - * getCacheDirectory will be used to control caching in the - * ImageInputStream that is created. - * - *

- * This method does not attempt to locate ImageReaders that can - * read directly from a URL; that may be accomplished using - * IIORegistry and ImageReaderSpi. - * - * @param input - * a URL to read from. - * - * @return a BufferedImage containing the decoded contents of the - * input, or null. - * - * @exception IllegalArgumentException - * if input is null. - * @exception IOException - * if an error occurs during reading. - */ - public static BufferedImage read(URL input) throws IOException { + public static BufferedImage readJS(URL input) throws IOException { return read(input.openStream()); } @@ -1431,43 +215,36 @@ public static BufferedImage read(URL input) throws IOException { * @exception IOException * if an error occurs during reading. */ - public static BufferedImage read(ImageInputStream stream) throws IOException { + public static BufferedImage readJS(ImageInputStream stream) throws IOException { JSUtil.notImplemented("ImageIO.read(ImageInputStream"); return null; } /** - * Writes an image using the an arbitrary ImageWriter that - * supports the given format to an ImageOutputStream. The image - * is written to the ImageOutputStream starting at the current - * stream pointer, overwriting existing stream data from that point forward, - * if present. + * Writes an image using the an arbitrary ImageWriter that supports + * the given format to an ImageOutputStream. The image is written + * to the ImageOutputStream starting at the current stream pointer, + * overwriting existing stream data from that point forward, if present. * *

* This method does not close the provided - * ImageOutputStream after the write operation has completed; it - * is the responsibility of the caller to close the stream, if desired. + * ImageOutputStream after the write operation has completed; it is + * the responsibility of the caller to close the stream, if desired. * - * @param im - * a RenderedImage to be written. - * @param formatName - * a String containg the informal name of the format. - * @param output - * an ImageOutputStream to be written to. + * @param im a RenderedImage to be written. + * @param formatName a String containg the informal name of the + * format. + * @param output an ImageOutputStream to be written to. * * @return false if no appropriate writer is found. * - * @exception IllegalArgumentException - * if any parameter is null. - * @exception IOException - * if an error occurs during writing. + * @exception IllegalArgumentException if any parameter is null. + * @exception IOException if an error occurs during writing. */ - public static boolean write(RenderedImage im, - String formatName, - ImageOutputStream output) throws IOException { - ImageWriter writer = getWriter(formatName); - return (writer != null && writer.write(im, null, (OutputStream) (Object) output)); - } + public static boolean writeJS(RenderedImage im, String formatName, ImageOutputStream output) throws IOException { + ImageWriter writer = getWriter(formatName); + return (writer != null && ((JSImageWriter) writer).write(im, null, (OutputStream) (Object) output)); + } /** * Writes an image using an arbitrary ImageWriter that supports @@ -1488,13 +265,10 @@ public static boolean write(RenderedImage im, * @exception IOException * if an error occurs during writing. */ - public static boolean write(RenderedImage im, String formatName, File output) + public static boolean writeJS(RenderedImage im, String formatName, File output) throws IOException { - if (im == null || output == null || formatName == null) { - throw new IllegalArgumentException("ImageIO.write(RenderedImage,String,File)"); - } ImageWriter writer = getWriter(formatName); - return (writer != null && writer.write(im, output.getName(), null)); + return (writer != null && ((JSImageWriter)writer).write(im, output.getName(), null)); } /** @@ -1524,13 +298,10 @@ public static boolean write(RenderedImage im, String formatName, File output) * @exception IOException * if an error occurs during writing. */ - public static boolean write(RenderedImage im, String formatName, + public static boolean writeJS(RenderedImage im, String formatName, OutputStream output) throws IOException { - if (im == null || output == null || formatName == null) { - throw new IllegalArgumentException("ImageIO.write(RenderedImage,String,OutputStream)"); - } ImageWriter writer = getWriter(formatName); - return (writer != null && writer.write(im, null, output)); + return (writer != null && ((JSImageWriter)writer).write(im, null, output)); } @@ -1541,13 +312,13 @@ public static boolean write(RenderedImage im, String formatName, */ private static ImageWriter getWriter(String formatName) { try { - return getImageWritersByFormatName(formatName).next(); + return (useSPI ? getImageWritersByFormatName(formatName) : getImageWritersByFormatNameJS(formatName)).next(); } catch (Exception e) { return null; } } - public static Iterator getImageWritersByFormatName(String formatName) { + public static Iterator getImageWritersByFormatNameJS(String formatName) { if (formatName == null) { throw new IllegalArgumentException("formatName == null!"); } @@ -1569,4 +340,1541 @@ public static Iterator getImageWritersByFormatName(String formatNam return set.iterator(); } -} + private static IIORegistry theRegistry; + + static IIORegistry getRegistry() { + + return (theRegistry == null ? theRegistry = IIORegistry.getDefaultInstance() : theRegistry); + } + + /** + * Constructor is private to prevent instantiation. + */ + private ImageIO() {} + + /** + * Scans for plug-ins on the application class path, + * loads their service provider classes, and registers a service + * provider instance for each one found with the + * IIORegistry. + * + *

This method is needed because the application class path can + * theoretically change, or additional plug-ins may become available. + * Rather than re-scanning the classpath on every invocation of the + * API, the class path is scanned automatically only on the first + * invocation. Clients can call this method to prompt a re-scan. + * Thus this method need only be invoked by sophisticated applications + * which dynamically make new plug-ins available at runtime. + * + *

The getResources method of the context + * ClassLoader is used locate JAR files containing + * files named + * META-INF/services/javax.imageio.spi.classname, + * where classname is one of ImageReaderSpi, + * ImageWriterSpi, ImageTranscoderSpi, + * ImageInputStreamSpi, or + * ImageOutputStreamSpi, along the application class + * path. + * + *

The contents of the located files indicate the names of + * actual implementation classes which implement the + * aforementioned service provider interfaces; the default class + * loader is then used to load each of these classes and to + * instantiate an instance of each class, which is then placed + * into the registry for later retrieval. + * + *

The exact set of locations searched depends on the + * implementation of the Java runtime environment. + * + * @see ClassLoader#getResources + */ + public static void scanForPlugins() { + getRegistry().registerApplicationClasspathSpis(); + } + + // ImageInputStreams + + /** + * A class to hold information about caching. Each + * ThreadGroup will have its own copy + * via the AppContext mechanism. + */ + static class CacheInfo { + boolean useCache = useSPI;// reconsider?? true; + File cacheDirectory = null; + Boolean hasPermission = Boolean.TRUE;// null; + + public CacheInfo() {} + + public boolean getUseCache() { + return useCache; + } + + public void setUseCache(boolean useCache) { + this.useCache = useCache; + } + + public File getCacheDirectory() { + return cacheDirectory; + } + + public void setCacheDirectory(File cacheDirectory) { + this.cacheDirectory = cacheDirectory; + } + + public Boolean getHasPermission() { + return hasPermission; + } + + public void setHasPermission(Boolean hasPermission) { + this.hasPermission = hasPermission; + } + } + + /** + * Returns the CacheInfo object associated with this + * ThreadGroup. + */ + private static synchronized CacheInfo getCacheInfo() { + AppContext context = AppContext.getAppContext(); + CacheInfo info = (CacheInfo)context.get(CacheInfo.class); + if (info == null) { + info = new CacheInfo(); + context.put(CacheInfo.class, info); + } + return info; + } + + /** + * Returns the default temporary (cache) directory as defined by the + * java.io.tmpdir system property. + */ + private static String getTempDir() { + GetPropertyAction a = new GetPropertyAction("java.io.tmpdir"); + return (String)AccessController.doPrivileged(a); + } + + /** + * Determines whether the caller has write access to the cache + * directory, stores the result in the CacheInfo object, + * and returns the decision. This method helps to prevent mysterious + * SecurityExceptions to be thrown when this convenience class is used + * in an applet, for example. + */ + private static boolean hasCachePermission() { + Boolean hasPermission = getCacheInfo().getHasPermission(); + + if (hasPermission != null) { + return hasPermission.booleanValue(); + } else { + try { + SecurityManager security = System.getSecurityManager(); + if (security != null) { + File cachedir = getCacheDirectory(); + String cachepath; + + if (cachedir != null) { + cachepath = cachedir.getPath(); + } else { + cachepath = getTempDir(); + + if (cachepath == null || cachepath.isEmpty()) { + getCacheInfo().setHasPermission(Boolean.FALSE); + return false; + } + } + + // we have to check whether we can read, write, + // and delete cache files. + // So, compose cache file path and check it. + String filepath = cachepath; + if (!filepath.endsWith(File.separator)) { + filepath += File.separator; + } + filepath += "*"; + + security.checkPermission(new FilePermission(filepath, "read, write, delete")); + } + } catch (SecurityException e) { + getCacheInfo().setHasPermission(Boolean.FALSE); + return false; + } + + getCacheInfo().setHasPermission(Boolean.TRUE); + return true; + } + } + + /** + * Sets a flag indicating whether a disk-based cache file should + * be used when creating ImageInputStreams and + * ImageOutputStreams. + * + *

When reading from a standard InputStream, it + * may be necessary to save previously read information in a cache + * since the underlying stream does not allow data to be re-read. + * Similarly, when writing to a standard + * OutputStream, a cache may be used to allow a + * previously written value to be changed before flushing it to + * the final destination. + * + *

The cache may reside in main memory or on disk. Setting + * this flag to false disallows the use of disk for + * future streams, which may be advantageous when working with + * small images, as the overhead of creating and destroying files + * is removed. + * + *

On startup, the value is set to true. + * + * @param useCache a boolean indicating whether a + * cache file should be used, in cases where it is optional. + * + * @see #getUseCache + */ + public static void setUseCache(boolean useCache) { + getCacheInfo().setUseCache(useCache); + } + + /** + * Returns the current value set by setUseCache, or + * true if no explicit setting has been made. + * + * @return true if a disk-based cache may be used for + * ImageInputStreams and + * ImageOutputStreams. + * + * @see #setUseCache + */ + public static boolean getUseCache() { + return getCacheInfo().getUseCache(); + } + + /** + * Sets the directory where cache files are to be created. A + * value of null indicates that the system-dependent + * default temporary-file directory is to be used. If + * getUseCache returns false, this value is ignored. + * + * @param cacheDirectory a File specifying a directory. + * + * @see File#createTempFile(String, String, File) + * + * @exception SecurityException if the security manager denies + * access to the directory. + * @exception IllegalArgumentException if cacheDir is + * non-null but is not a directory. + * + * @see #getCacheDirectory + */ + public static void setCacheDirectory(File cacheDirectory) { + if ((cacheDirectory != null) && !(cacheDirectory.isDirectory())) { + throw new IllegalArgumentException("Not a directory!"); + } + getCacheInfo().setCacheDirectory(cacheDirectory); + getCacheInfo().setHasPermission(null); + } + + /** + * Returns the current value set by + * setCacheDirectory, or null if no + * explicit setting has been made. + * + * @return a File indicating the directory where + * cache files will be created, or null to indicate + * the system-dependent default temporary-file directory. + * + * @see #setCacheDirectory + */ + public static File getCacheDirectory() { + return getCacheInfo().getCacheDirectory(); + } + + /** + * Returns an ImageInputStream that will take its + * input from the given Object. The set of + * ImageInputStreamSpis registered with the + * IIORegistry class is queried and the first one + * that is able to take input from the supplied object is used to + * create the returned ImageInputStream. If no + * suitable ImageInputStreamSpi exists, + * null is returned. + * + *

The current cache settings from getUseCacheand + * getCacheDirectory will be used to control caching. + * + * @param input an Object to be used as an input + * source, such as a File, readable + * RandomAccessFile, or InputStream. + * + * @return an ImageInputStream, or null. + * + * @exception IllegalArgumentException if input + * is null. + * @exception IOException if a cache file is needed but cannot be + * created. + * + * @see javax.imageio.spi.ImageInputStreamSpi + */ + public static ImageInputStream createImageInputStream(Object input) + throws IOException { + if (input == null) { + throw new IllegalArgumentException("input == null!"); + } + + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageInputStreamSpi.class, + true); + } catch (IllegalArgumentException e) { + return null; + } + + boolean usecache = getUseCache() && hasCachePermission(); + + while (iter.hasNext()) { + ImageInputStreamSpi spi = (ImageInputStreamSpi)iter.next(); + if (spi.getInputClass().isInstance(input)) { + try { + return spi.createInputStreamInstance(input, + usecache, + getCacheDirectory()); + } catch (IOException e) { + throw new IIOException("Can't create cache file!", e); + } + } + } + + return null; + } + + // ImageOutputStreams + + /** + * Returns an ImageOutputStream that will send its + * output to the given Object. The set of + * ImageOutputStreamSpis registered with the + * IIORegistry class is queried and the first one + * that is able to send output from the supplied object is used to + * create the returned ImageOutputStream. If no + * suitable ImageOutputStreamSpi exists, + * null is returned. + * + *

The current cache settings from getUseCacheand + * getCacheDirectory will be used to control caching. + * + * @param output an Object to be used as an output + * destination, such as a File, writable + * RandomAccessFile, or OutputStream. + * + * @return an ImageOutputStream, or + * null. + * + * @exception IllegalArgumentException if output is + * null. + * @exception IOException if a cache file is needed but cannot be + * created. + * + * @see javax.imageio.spi.ImageOutputStreamSpi + */ + public static ImageOutputStream createImageOutputStream(Object output) + throws IOException { + if (output == null) { + throw new IllegalArgumentException("output == null!"); + } + + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageOutputStreamSpi.class, + true); + } catch (IllegalArgumentException e) { + return null; + } + + boolean usecache = getUseCache() && hasCachePermission(); + + while (iter.hasNext()) { + ImageOutputStreamSpi spi = (ImageOutputStreamSpi)iter.next(); + if (spi.getOutputClass().isInstance(output)) { + try { + return spi.createOutputStreamInstance(output, + usecache, + getCacheDirectory()); + } catch (IOException e) { + throw new IIOException("Can't create cache file!", e); + } + } + } + + return null; + } + + private static enum SpiInfo { + FORMAT_NAMES { + @Override + String[] info(ImageReaderWriterSpi spi) { + return spi.getFormatNames(); + } + }, + MIME_TYPES { + @Override + String[] info(ImageReaderWriterSpi spi) { + return spi.getMIMETypes(); + } + }, + FILE_SUFFIXES { + @Override + String[] info(ImageReaderWriterSpi spi) { + return spi.getFileSuffixes(); + } + }; + + abstract String[] info(ImageReaderWriterSpi spi); + } + + private static + String[] getReaderWriterInfo(Class spiClass, SpiInfo spiInfo) + { + // Ensure category is present + Iterator iter; + try { + iter = getRegistry().getServiceProviders(spiClass, true); + } catch (IllegalArgumentException e) { + return new String[0]; + } + + HashSet s = new HashSet(); + while (iter.hasNext()) { + ImageReaderWriterSpi spi = iter.next(); + Collections.addAll(s, spiInfo.info(spi)); + } + + return s.toArray(new String[s.size()]); + } + + // Readers + + static class ImageReaderIterator implements Iterator { + // Contains ImageReaderSpis + public Iterator iter; + + public ImageReaderIterator(Iterator iter) { + this.iter = iter; + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public ImageReader next() { + ImageReaderSpi spi = null; + try { + spi = (ImageReaderSpi)iter.next(); + return spi.createReaderInstance(); + } catch (IOException e) { + // Deregister the spi in this case, but only as + // an ImageReaderSpi + getRegistry().deregisterServiceProvider(spi, ImageReaderSpi.class); + } + return null; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + static class CanDecodeInputFilter + implements ServiceRegistry.Filter { + + Object input; + + public CanDecodeInputFilter(Object input) { + this.input = input; + } + + public boolean filter(Object elt) { + try { + ImageReaderSpi spi = (ImageReaderSpi)elt; + ImageInputStream stream = null; + if (input instanceof ImageInputStream) { + stream = (ImageInputStream)input; + } + + // Perform mark/reset as a defensive measure + // even though plug-ins are supposed to take + // care of it. + boolean canDecode = false; + if (stream != null) { + stream.mark(); + } + canDecode = spi.canDecodeInput(input); + if (stream != null) { + stream.reset(); + } + + return canDecode; + } catch (IOException e) { + return false; + } + } + } + + static class CanEncodeImageAndFormatFilter + implements ServiceRegistry.Filter { + + ImageTypeSpecifier type; + String formatName; + + public CanEncodeImageAndFormatFilter(ImageTypeSpecifier type, + String formatName) { + this.type = type; + this.formatName = formatName; + } + + public boolean filter(Object elt) { + ImageWriterSpi spi = (ImageWriterSpi)elt; + return Arrays.asList(spi.getFormatNames()).contains(formatName) && + spi.canEncodeImage(type); + } + } + + static class ContainsFilter + implements ServiceRegistry.Filter { + + Method method; + String name; + + // method returns an array of Strings + public ContainsFilter(Method method, + String name) { + this.method = method; + this.name = name; + } + + public boolean filter(Object elt) { + try { + return contains((String[])method.invoke(elt), name); + } catch (Exception e) { + return false; + } + } + } + + /** + * Returns an Iterator containing all currently + * registered ImageReaders that claim to be able to + * decode the supplied Object, typically an + * ImageInputStream. + * + *

The stream position is left at its prior position upon + * exit from this method. + * + * @param input an ImageInputStream or other + * Object containing encoded image data. + * + * @return an Iterator containing ImageReaders. + * + * @exception IllegalArgumentException if input is + * null. + * + * @see javax.imageio.spi.ImageReaderSpi#canDecodeInput + */ + public static Iterator getImageReaders(Object input) { + if (input == null) { + throw new IllegalArgumentException("input == null!"); + } + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageReaderSpi.class, + new CanDecodeInputFilter(input), + true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + + return new ImageReaderIterator(iter); + } + + private static Method readerFormatNamesMethod; + private static Method readerFileSuffixesMethod; + private static Method readerMIMETypesMethod; + private static Method writerFormatNamesMethod; + private static Method writerFileSuffixesMethod; + private static Method writerMIMETypesMethod; + + static { + try { + if (useSPI) { + readerFormatNamesMethod = ImageReaderSpi.class.getMethod("getFormatNames"); + readerFileSuffixesMethod = ImageReaderSpi.class.getMethod("getFileSuffixes"); + readerMIMETypesMethod = ImageReaderSpi.class.getMethod("getMIMETypes"); + + writerFormatNamesMethod = ImageWriterSpi.class.getMethod("getFormatNames"); + writerFileSuffixesMethod = ImageWriterSpi.class.getMethod("getFileSuffixes"); + writerMIMETypesMethod = ImageWriterSpi.class.getMethod("getMIMETypes"); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } + + /** + * Returns an Iterator containing all currently + * registered ImageReaders that claim to be able to + * decode the named format. + * + * @param formatName a String containing the informal + * name of a format (e.g., "jpeg" or "tiff". + * + * @return an Iterator containing + * ImageReaders. + * + * @exception IllegalArgumentException if formatName + * is null. + * + * @see javax.imageio.spi.ImageReaderSpi#getFormatNames + */ + public static Iterator + getImageReadersByFormatName(String formatName) + { + if (formatName == null) { + throw new IllegalArgumentException("formatName == null!"); + } + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageReaderSpi.class, + new ContainsFilter(readerFormatNamesMethod, + formatName), + true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + return new ImageReaderIterator(iter); + } + + /** + * Returns an Iterator containing all currently + * registered ImageReaders that claim to be able to + * decode files with the given suffix. + * + * @param fileSuffix a String containing a file + * suffix (e.g., "jpg" or "tiff"). + * + * @return an Iterator containing + * ImageReaders. + * + * @exception IllegalArgumentException if fileSuffix + * is null. + * + * @see javax.imageio.spi.ImageReaderSpi#getFileSuffixes + */ + public static Iterator + getImageReadersBySuffix(String fileSuffix) + { + if (fileSuffix == null) { + throw new IllegalArgumentException("fileSuffix == null!"); + } + // Ensure category is present + Iterator iter; + try { + iter = getRegistry().getServiceProviders(ImageReaderSpi.class, + new ContainsFilter(readerFileSuffixesMethod, + fileSuffix), + true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + return new ImageReaderIterator(iter); + } + + /** + * Returns an Iterator containing all currently + * registered ImageReaders that claim to be able to + * decode files with the given MIME type. + * + * @param MIMEType a String containing a file + * suffix (e.g., "image/jpeg" or "image/x-bmp"). + * + * @return an Iterator containing + * ImageReaders. + * + * @exception IllegalArgumentException if MIMEType is + * null. + * + * @see javax.imageio.spi.ImageReaderSpi#getMIMETypes + */ + public static Iterator + getImageReadersByMIMEType(String MIMEType) + { + if (MIMEType == null) { + throw new IllegalArgumentException("MIMEType == null!"); + } + // Ensure category is present + Iterator iter; + try { + iter = getRegistry().getServiceProviders(ImageReaderSpi.class, + new ContainsFilter(readerMIMETypesMethod, + MIMEType), + true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + return new ImageReaderIterator(iter); + } + + // Writers + + /** + * Returns an array of Strings listing all of the + * informal format names understood by the current set of registered + * writers. + * + * @return an array of Strings. + */ + public static String[] getWriterFormatNames() { + return getReaderWriterInfo(ImageWriterSpi.class, + SpiInfo.FORMAT_NAMES); + } + + /** + * Returns an array of Strings listing all of the + * MIME types understood by the current set of registered + * writers. + * + * @return an array of Strings. + */ + public static String[] getWriterMIMETypes() { + return getReaderWriterInfo(ImageWriterSpi.class, + SpiInfo.MIME_TYPES); + } + + /** + * Returns an array of Strings listing all of the + * file suffixes associated with the formats understood + * by the current set of registered writers. + * + * @return an array of Strings. + * @since 1.6 + */ + public static String[] getWriterFileSuffixes() { + return getReaderWriterInfo(ImageWriterSpi.class, + SpiInfo.FILE_SUFFIXES); + } + + static class ImageWriterIterator implements Iterator { + // Contains ImageWriterSpis + public Iterator iter; + + public ImageWriterIterator(Iterator iter) { + this.iter = iter; + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public ImageWriter next() { + ImageWriterSpi spi = null; + try { + spi = (ImageWriterSpi)iter.next(); + return spi.createWriterInstance(); + } catch (IOException e) { + // Deregister the spi in this case, but only as a writerSpi + getRegistry().deregisterServiceProvider(spi, ImageWriterSpi.class); + } + return null; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private static boolean contains(String[] names, String name) { + for (int i = 0; i < names.length; i++) { + if (name.equalsIgnoreCase(names[i])) { + return true; + } + } + + return false; + } + + /** + * Returns an Iterator containing all currently + * registered ImageWriters that claim to be able to + * encode the named format. + * + * @param formatName a String containing the informal + * name of a format (e.g., "jpeg" or "tiff". + * + * @return an Iterator containing + * ImageWriters. + * + * @exception IllegalArgumentException if formatName is + * null. + * + * @see javax.imageio.spi.ImageWriterSpi#getFormatNames + */ + public static Iterator + getImageWritersByFormatName(String formatName) + { + if (formatName == null) { + throw new IllegalArgumentException("formatName == null!"); + } + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageWriterSpi.class, + new ContainsFilter(writerFormatNamesMethod, + formatName), + true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + return new ImageWriterIterator(iter); + } + + /** + * Returns an Iterator containing all currently + * registered ImageWriters that claim to be able to + * encode files with the given suffix. + * + * @param fileSuffix a String containing a file + * suffix (e.g., "jpg" or "tiff"). + * + * @return an Iterator containing ImageWriters. + * + * @exception IllegalArgumentException if fileSuffix is + * null. + * + * @see javax.imageio.spi.ImageWriterSpi#getFileSuffixes + */ + public static Iterator + getImageWritersBySuffix(String fileSuffix) + { + if (fileSuffix == null) { + throw new IllegalArgumentException("fileSuffix == null!"); + } + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageWriterSpi.class, + new ContainsFilter(writerFileSuffixesMethod, + fileSuffix), + true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + return new ImageWriterIterator(iter); + } + + /** + * Returns an Iterator containing all currently + * registered ImageWriters that claim to be able to + * encode files with the given MIME type. + * + * @param MIMEType a String containing a file + * suffix (e.g., "image/jpeg" or "image/x-bmp"). + * + * @return an Iterator containing ImageWriters. + * + * @exception IllegalArgumentException if MIMEType is + * null. + * + * @see javax.imageio.spi.ImageWriterSpi#getMIMETypes + */ + public static Iterator + getImageWritersByMIMEType(String MIMEType) + { + if (MIMEType == null) { + throw new IllegalArgumentException("MIMEType == null!"); + } + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageWriterSpi.class, + new ContainsFilter(writerMIMETypesMethod, + MIMEType), + true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + return new ImageWriterIterator(iter); + } + + /** + * Returns an ImageWritercorresponding to the given + * ImageReader, if there is one, or null + * if the plug-in for this ImageReader does not + * specify a corresponding ImageWriter, or if the + * given ImageReader is not registered. This + * mechanism may be used to obtain an ImageWriter + * that will understand the internal structure of non-pixel + * metadata (as encoded by IIOMetadata objects) + * generated by the ImageReader. By obtaining this + * data from the ImageReader and passing it on to the + * ImageWriter obtained with this method, a client + * program can read an image, modify it in some way, and write it + * back out preserving all metadata, without having to understand + * anything about the structure of the metadata, or even about + * the image format. Note that this method returns the + * "preferred" writer, which is the first in the list returned by + * javax.imageio.spi.ImageReaderSpi.getImageWriterSpiNames(). + * + * @param reader an instance of a registered ImageReader. + * + * @return an ImageWriter, or null. + * + * @exception IllegalArgumentException if reader is + * null. + * + * @see #getImageReader(ImageWriter) + * @see javax.imageio.spi.ImageReaderSpi#getImageWriterSpiNames() + */ + public static ImageWriter getImageWriter(ImageReader reader) { + if (reader == null) { + throw new IllegalArgumentException("reader == null!"); + } + + ImageReaderSpi readerSpi = reader.getOriginatingProvider(); + if (readerSpi == null) { + Iterator readerSpiIter; + // Ensure category is present + try { + readerSpiIter = + getRegistry().getServiceProviders(ImageReaderSpi.class, + false); + } catch (IllegalArgumentException e) { + return null; + } + + while (readerSpiIter.hasNext()) { + ImageReaderSpi temp = (ImageReaderSpi) readerSpiIter.next(); + if (temp.isOwnReader(reader)) { + readerSpi = temp; + break; + } + } + if (readerSpi == null) { + return null; + } + } + + String[] writerNames = readerSpi.getImageWriterSpiNames(); + if (writerNames == null) { + return null; + } + + Class writerSpiClass = null; + try { + writerSpiClass = Class.forName(writerNames[0], true, + ClassLoader.getSystemClassLoader()); + } catch (ClassNotFoundException e) { + return null; + } + + ImageWriterSpi writerSpi = (ImageWriterSpi) + getRegistry().getServiceProviderByClass(writerSpiClass); + if (writerSpi == null) { + return null; + } + + try { + return writerSpi.createWriterInstance(); + } catch (IOException e) { + // Deregister the spi in this case, but only as a writerSpi + getRegistry().deregisterServiceProvider(writerSpi, + ImageWriterSpi.class); + return null; + } + } + + /** + * Returns an ImageReadercorresponding to the given + * ImageWriter, if there is one, or null + * if the plug-in for this ImageWriter does not + * specify a corresponding ImageReader, or if the + * given ImageWriter is not registered. This method + * is provided principally for symmetry with + * getImageWriter(ImageReader). Note that this + * method returns the "preferred" reader, which is the first in + * the list returned by + * javax.imageio.spi.ImageWriterSpi.getImageReaderSpiNames(). + * + * @param writer an instance of a registered ImageWriter. + * + * @return an ImageReader, or null. + * + * @exception IllegalArgumentException if writer is + * null. + * + * @see #getImageWriter(ImageReader) + * @see javax.imageio.spi.ImageWriterSpi#getImageReaderSpiNames() + */ + public static ImageReader getImageReader(ImageWriter writer) { + if (writer == null) { + throw new IllegalArgumentException("writer == null!"); + } + + ImageWriterSpi writerSpi = writer.getOriginatingProvider(); + if (writerSpi == null) { + Iterator writerSpiIter; + // Ensure category is present + try { + writerSpiIter = + getRegistry().getServiceProviders(ImageWriterSpi.class, + false); + } catch (IllegalArgumentException e) { + return null; + } + + while (writerSpiIter.hasNext()) { + ImageWriterSpi temp = (ImageWriterSpi) writerSpiIter.next(); + if (temp.isOwnWriter(writer)) { + writerSpi = temp; + break; + } + } + if (writerSpi == null) { + return null; + } + } + + String[] readerNames = writerSpi.getImageReaderSpiNames(); + if (readerNames == null) { + return null; + } + + Class readerSpiClass = null; + try { + readerSpiClass = Class.forName(readerNames[0], true, + ClassLoader.getSystemClassLoader()); + } catch (ClassNotFoundException e) { + return null; + } + + ImageReaderSpi readerSpi = (ImageReaderSpi) + getRegistry().getServiceProviderByClass(readerSpiClass); + if (readerSpi == null) { + return null; + } + + try { + return readerSpi.createReaderInstance(); + } catch (IOException e) { + // Deregister the spi in this case, but only as a readerSpi + getRegistry().deregisterServiceProvider(readerSpi, + ImageReaderSpi.class); + return null; + } + } + + /** + * Returns an Iterator containing all currently + * registered ImageWriters that claim to be able to + * encode images of the given layout (specified using an + * ImageTypeSpecifier) in the given format. + * + * @param type an ImageTypeSpecifier indicating the + * layout of the image to be written. + * @param formatName the informal name of the format. + * + * @return an Iterator containing ImageWriters. + * + * @exception IllegalArgumentException if any parameter is + * null. + * + * @see javax.imageio.spi.ImageWriterSpi#canEncodeImage(ImageTypeSpecifier) + */ + public static Iterator + getImageWriters(ImageTypeSpecifier type, String formatName) + { + if (type == null) { + throw new IllegalArgumentException("type == null!"); + } + if (formatName == null) { + throw new IllegalArgumentException("formatName == null!"); + } + + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageWriterSpi.class, + new CanEncodeImageAndFormatFilter(type, + formatName), + true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + + return new ImageWriterIterator(iter); + } + + static class ImageTranscoderIterator + implements Iterator + { + // Contains ImageTranscoderSpis + public Iterator iter; + + public ImageTranscoderIterator(Iterator iter) { + this.iter = iter; + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public ImageTranscoder next() { + ImageTranscoderSpi spi = null; + spi = (ImageTranscoderSpi)iter.next(); + return spi.createTranscoderInstance(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + static class TranscoderFilter + implements ServiceRegistry.Filter { + + String readerSpiName; + String writerSpiName; + + public TranscoderFilter(ImageReaderSpi readerSpi, + ImageWriterSpi writerSpi) { + this.readerSpiName = readerSpi.getClass().getName(); + this.writerSpiName = writerSpi.getClass().getName(); + } + + public boolean filter(Object elt) { + ImageTranscoderSpi spi = (ImageTranscoderSpi)elt; + String readerName = spi.getReaderServiceProviderName(); + String writerName = spi.getWriterServiceProviderName(); + return (readerName.equals(readerSpiName) && + writerName.equals(writerSpiName)); + } + } + + /** + * Returns an Iterator containing all currently + * registered ImageTranscoders that claim to be + * able to transcode between the metadata of the given + * ImageReader and ImageWriter. + * + * @param reader an ImageReader. + * @param writer an ImageWriter. + * + * @return an Iterator containing + * ImageTranscoders. + * + * @exception IllegalArgumentException if reader or + * writer is null. + */ + public static Iterator + getImageTranscoders(ImageReader reader, ImageWriter writer) + { + if (reader == null) { + throw new IllegalArgumentException("reader == null!"); + } + if (writer == null) { + throw new IllegalArgumentException("writer == null!"); + } + ImageReaderSpi readerSpi = reader.getOriginatingProvider(); + ImageWriterSpi writerSpi = writer.getOriginatingProvider(); + ServiceRegistry.Filter filter = + new TranscoderFilter(readerSpi, writerSpi); + + Iterator iter; + // Ensure category is present + try { + iter = getRegistry().getServiceProviders(ImageTranscoderSpi.class, + filter, true); + } catch (IllegalArgumentException e) { + return Collections.emptyIterator(); + } + return new ImageTranscoderIterator(iter); + } + + // All-in-one methods + + /** + * Returns a BufferedImage as the result of decoding + * a supplied File with an ImageReader + * chosen automatically from among those currently registered. + * The File is wrapped in an + * ImageInputStream. If no registered + * ImageReader claims to be able to read the + * resulting stream, null is returned. + * + *

The current cache settings from getUseCacheand + * getCacheDirectory will be used to control caching in the + * ImageInputStream that is created. + * + *

Note that there is no read method that takes a + * filename as a String; use this method instead after + * creating a File from the filename. + * + *

This method does not attempt to locate + * ImageReaders that can read directly from a + * File; that may be accomplished using + * IIORegistry and ImageReaderSpi. + * + * @param input a File to read from. + * + * @return a BufferedImage containing the decoded + * contents of the input, or null. + * + * @exception IllegalArgumentException if input is + * null. + * @exception IOException if an error occurs during reading. + */ + public static BufferedImage read(File input) throws IOException { + if (input == null) { + throw new IllegalArgumentException("input == null!"); + } + if (!input.canRead()) { + throw new IIOException("Can't read input file!"); + } + + ImageInputStream stream = createImageInputStream(input); + if (stream == null) { + throw new IIOException("Can't create an ImageInputStream!"); + } + BufferedImage bi = read(stream); + if (bi == null) { + stream.close(); + } + return bi; + } + + /** + * Returns a BufferedImage as the result of decoding + * a supplied InputStream with an ImageReader + * chosen automatically from among those currently registered. + * The InputStream is wrapped in an + * ImageInputStream. If no registered + * ImageReader claims to be able to read the + * resulting stream, null is returned. + * + *

The current cache settings from getUseCacheand + * getCacheDirectory will be used to control caching in the + * ImageInputStream that is created. + * + *

This method does not attempt to locate + * ImageReaders that can read directly from an + * InputStream; that may be accomplished using + * IIORegistry and ImageReaderSpi. + * + *

This method does not close the provided + * InputStream after the read operation has completed; + * it is the responsibility of the caller to close the stream, if desired. + * + * @param input an InputStream to read from. + * + * @return a BufferedImage containing the decoded + * contents of the input, or null. + * + * @exception IllegalArgumentException if input is + * null. + * @exception IOException if an error occurs during reading. + */ + public static BufferedImage read(InputStream input) throws IOException { + if (input == null) { + throw new IllegalArgumentException("input == null!"); + } + if (!useSPI) + return readJS(input); + + ImageInputStream stream = createImageInputStream(input); + BufferedImage bi = read(stream); + if (bi == null) { + stream.close(); + } + return bi; + } + + public static BufferedImage readJS(InputStream input) throws IOException { + return (BufferedImage) Toolkit + .getDefaultToolkit() + .createImage( + JSUtil + .getSignedStreamBytes(input instanceof BufferedInputStream ? (BufferedInputStream) input + : new BufferedInputStream(input))); + } + + /** + * Returns a BufferedImage as the result of decoding + * a supplied URL with an ImageReader + * chosen automatically from among those currently registered. An + * InputStream is obtained from the URL, + * which is wrapped in an ImageInputStream. If no + * registered ImageReader claims to be able to read + * the resulting stream, null is returned. + * + *

The current cache settings from getUseCacheand + * getCacheDirectory will be used to control caching in the + * ImageInputStream that is created. + * + *

This method does not attempt to locate + * ImageReaders that can read directly from a + * URL; that may be accomplished using + * IIORegistry and ImageReaderSpi. + * + * @param input a URL to read from. + * + * @return a BufferedImage containing the decoded + * contents of the input, or null. + * + * @exception IllegalArgumentException if input is + * null. + * @exception IOException if an error occurs during reading. + */ + public static BufferedImage read(URL input) throws IOException { + if (input == null) { + throw new IllegalArgumentException("input == null!"); + } + if (!useSPI) + return readJS(input); + + InputStream istream = null; + try { + istream = input.openStream(); + } catch (IOException e) { + throw new IIOException("Can't get input stream from URL!", e); + } + ImageInputStream stream = createImageInputStream(istream); + BufferedImage bi; + try { + bi = read(stream); + if (bi == null) { + stream.close(); + } + } finally { + istream.close(); + } + return bi; + } + + /** + * Returns a BufferedImage as the result of decoding + * a supplied ImageInputStream with an + * ImageReader chosen automatically from among those + * currently registered. If no registered + * ImageReader claims to be able to read the stream, + * null is returned. + * + *

Unlike most other methods in this class, this method does + * close the provided ImageInputStream after the read + * operation has completed, unless null is returned, + * in which case this method does not close the stream. + * + * @param stream an ImageInputStream to read from. + * + * @return a BufferedImage containing the decoded + * contents of the input, or null. + * + * @exception IllegalArgumentException if stream is + * null. + * @exception IOException if an error occurs during reading. + */ + public static BufferedImage read(ImageInputStream stream) + throws IOException { + if (stream == null) { + throw new IllegalArgumentException("stream == null!"); + } + + Iterator iter = getImageReaders(stream); + if (!iter.hasNext()) { + return null; + } + + ImageReader reader = (ImageReader)iter.next(); + ImageReadParam param = reader.getDefaultReadParam(); + reader.setInput(stream, true, true); + BufferedImage bi; + try { + bi = reader.read(0, param); + } finally { + reader.dispose(); + stream.close(); + } + return bi; + } + + /** + * Writes an image using the an arbitrary ImageWriter + * that supports the given format to an + * ImageOutputStream. The image is written to the + * ImageOutputStream starting at the current stream + * pointer, overwriting existing stream data from that point + * forward, if present. + * + *

This method does not close the provided + * ImageOutputStream after the write operation has completed; + * it is the responsibility of the caller to close the stream, if desired. + * + * @param im a RenderedImage to be written. + * @param formatName a String containing the informal + * name of the format. + * @param output an ImageOutputStream to be written to. + * + * @return false if no appropriate writer is found. + * + * @exception IllegalArgumentException if any parameter is + * null. + * @exception IOException if an error occurs during writing. + */ + public static boolean write(RenderedImage im, + String formatName, + ImageOutputStream output) throws IOException { + if (im == null) { + throw new IllegalArgumentException("im == null!"); + } + if (formatName == null) { + throw new IllegalArgumentException("formatName == null!"); + } + if (output == null) { + throw new IllegalArgumentException("output == null!"); + } + return useSPI ? doWrite(im, getWriter(im, formatName), output) : writeJS(im, formatName, output); + } + + /** + * Writes an image using an arbitrary ImageWriter + * that supports the given format to a File. If + * there is already a File present, its contents are + * discarded. + * + * @param im a RenderedImage to be written. + * @param formatName a String containing the informal + * name of the format. + * @param output a File to be written to. + * + * @return false if no appropriate writer is found. + * + * @exception IllegalArgumentException if any parameter is + * null. + * @exception IOException if an error occurs during writing. + */ + public static boolean write(RenderedImage im, + String formatName, + File output) throws IOException { + if (output == null) { + throw new IllegalArgumentException("output == null!"); + } + if (!useSPI) + return writeJS(im, formatName, output); + + ImageOutputStream stream = null; + + ImageWriter writer = getWriter(im, formatName); + if (writer == null) { + /* Do not make changes in the file system if we have + * no appropriate writer. + */ + return false; + } + + try { + output.delete(); + stream = createImageOutputStream(output); + } catch (IOException e) { + throw new IIOException("Can't create output stream!", e); + } + + try { + return doWrite(im, writer, stream); + } finally { + stream.close(); + } + } + + /** + * Writes an image using an arbitrary ImageWriter + * that supports the given format to an OutputStream. + * + *

This method does not close the provided + * OutputStream after the write operation has completed; + * it is the responsibility of the caller to close the stream, if desired. + * + *

The current cache settings from getUseCacheand + * getCacheDirectory will be used to control caching. + * + * @param im a RenderedImage to be written. + * @param formatName a String containing the informal + * name of the format. + * @param output an OutputStream to be written to. + * + * @return false if no appropriate writer is found. + * + * @exception IllegalArgumentException if any parameter is + * null. + * @exception IOException if an error occurs during writing. + */ + public static boolean write(RenderedImage im, + String formatName, + OutputStream output) throws IOException { + if (output == null) { + throw new IllegalArgumentException("output == null!"); + } + + if (!useSPI) + return writeJS(im, formatName, output); + + + ImageOutputStream stream = null; + try { + stream = createImageOutputStream(output); + } catch (IOException e) { + throw new IIOException("Can't create output stream!", e); + } + + try { + return doWrite(im, getWriter(im, formatName), stream); + } finally { + stream.close(); + } + } + + /** + * Returns ImageWriter instance according to given + * rendered image and image format or null if there + * is no appropriate writer. + */ + private static ImageWriter getWriter(RenderedImage im, + String formatName) { + ImageTypeSpecifier type = + ImageTypeSpecifier.createFromRenderedImage(im); + Iterator iter = getImageWriters(type, formatName); + + if (iter.hasNext()) { + return iter.next(); + } else { + return null; + } + } + + /** + * Writes image to output stream using given image writer. + */ + private static boolean doWrite(RenderedImage im, ImageWriter writer, + ImageOutputStream output) throws IOException { + if (writer == null) { + return false; + } + writer.setOutput(output); + try { + writer.write(im); + } finally { + writer.dispose(); + output.flush(); + } + return true; + } + } diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/ImageReadParam.java b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageReadParam.java new file mode 100644 index 000000000..bc706e719 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageReadParam.java @@ -0,0 +1,508 @@ +/* + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio; + +import java.awt.Dimension; +import java.awt.image.BufferedImage; + +/** + * A class describing how a stream is to be decoded. Instances of + * this class or its subclasses are used to supply prescriptive + * "how-to" information to instances of ImageReader. + * + *

An image encoded as part of a file or stream may be thought of + * extending out in multiple dimensions: the spatial dimensions of + * width and height, a number of bands, and a number of progressive + * decoding passes. This class allows a contiguous (hyper)rectangular + * subarea of the image in all of these dimensions to be selected for + * decoding. Additionally, the spatial dimensions may be subsampled + * discontinuously. Finally, color and format conversions may be + * specified by controlling the ColorModel and + * SampleModel of the destination image, either by + * providing a BufferedImage or by using an + * ImageTypeSpecifier. + * + *

An ImageReadParam object is used to specify how an + * image, or a set of images, will be converted on input from + * a stream in the context of the Java Image I/O framework. A plug-in for a + * specific image format will return instances of + * ImageReadParam from the + * getDefaultReadParam method of its + * ImageReader implementation. + * + *

The state maintained by an instance of + * ImageReadParam is independent of any particular image + * being decoded. When actual decoding takes place, the values set in + * the read param are combined with the actual properties of the image + * being decoded from the stream and the destination + * BufferedImage that will receive the decoded pixel + * data. For example, the source region set using + * setSourceRegion will first be intersected with the + * actual valid source area. The result will be translated by the + * value returned by getDestinationOffset, and the + * resulting rectangle intersected with the actual valid destination + * area to yield the destination area that will be written. + * + *

The parameters specified by an ImageReadParam are + * applied to an image as follows. First, if a rendering size has + * been set by setSourceRenderSize, the entire decoded + * image is rendered at the size given by + * getSourceRenderSize. Otherwise, the image has its + * natural size given by ImageReader.getWidth and + * ImageReader.getHeight. + * + *

Next, the image is clipped against the source region + * specified by getSourceXOffset, getSourceYOffset, + * getSourceWidth, and getSourceHeight. + * + *

The resulting region is then subsampled according to the + * factors given in {@link IIOParam#setSourceSubsampling + * IIOParam.setSourceSubsampling}. The first pixel, + * the number of pixels per row, and the number of rows all depend + * on the subsampling settings. + * Call the minimum X and Y coordinates of the resulting rectangle + * (minX, minY), its width w + * and its height h. + * + *

This rectangle is offset by + * (getDestinationOffset().x, + * getDestinationOffset().y) and clipped against the + * destination bounds. If no destination image has been set, the + * destination is defined to have a width of + * getDestinationOffset().x + w, and a + * height of getDestinationOffset().y + h so + * that all pixels of the source region may be written to the + * destination. + * + *

Pixels that land, after subsampling, within the destination + * image, and that are written in one of the progressive passes + * specified by getSourceMinProgressivePass and + * getSourceNumProgressivePasses are passed along to the + * next step. + * + *

Finally, the source samples of each pixel are mapped into + * destination bands according to the algorithm described in the + * comment for setDestinationBands. + * + *

Plug-in writers may extend the functionality of + * ImageReadParam by providing a subclass that implements + * additional, plug-in specific interfaces. It is up to the plug-in + * to document what interfaces are available and how they are to be + * used. Readers will silently ignore any extended features of an + * ImageReadParam subclass of which they are not aware. + * Also, they may ignore any optional features that they normally + * disable when creating their own ImageReadParam + * instances via getDefaultReadParam. + * + *

Note that unless a query method exists for a capability, it must + * be supported by all ImageReader implementations + * (e.g. source render size is optional, but subsampling must be + * supported). + * + * + * @see ImageReader + * @see ImageWriter + * @see ImageWriteParam + */ +public class ImageReadParam extends IIOParam { + + /** + * true if this ImageReadParam allows + * the source rendering dimensions to be set. By default, the + * value is false. Subclasses must set this value + * manually. + * + *

ImageReaders that do not support setting of + * the source render size should set this value to + * false. + */ + protected boolean canSetSourceRenderSize = false; + + /** + * The desired rendering width and height of the source, if + * canSetSourceRenderSize is true, or + * null. + * + *

ImageReaders that do not support setting of + * the source render size may ignore this value. + */ + protected Dimension sourceRenderSize = null; + + /** + * The current destination BufferedImage, or + * null if none has been set. By default, the value + * is null. + */ + protected BufferedImage destination = null; + + /** + * The set of destination bands to be used, as an array of + * ints. By default, the value is null, + * indicating all destination bands should be written in order. + */ + protected int[] destinationBands = null; + + /** + * The minimum index of a progressive pass to read from the + * source. By default, the value is set to 0, which indicates + * that passes starting with the first available pass should be + * decoded. + * + *

Subclasses should ensure that this value is + * non-negative. + */ + protected int minProgressivePass = 0; + + /** + * The maximum number of progressive passes to read from the + * source. By default, the value is set to + * Integer.MAX_VALUE, which indicates that passes up + * to and including the last available pass should be decoded. + * + *

Subclasses should ensure that this value is positive. + * Additionally, if the value is not + * Integer.MAX_VALUE, then minProgressivePass + + * numProgressivePasses - 1 should not exceed + * Integer.MAX_VALUE. + */ + protected int numProgressivePasses = Integer.MAX_VALUE; + + /** + * Constructs an ImageReadParam. + */ + public ImageReadParam() {} + + // Comment inherited + public void setDestinationType(ImageTypeSpecifier destinationType) { + super.setDestinationType(destinationType); + setDestination(null); + } + + /** + * Supplies a BufferedImage to be used as the + * destination for decoded pixel data. The currently set image + * will be written to by the read, + * readAll, and readRaster methods, and + * a reference to it will be returned by those methods. + * + *

Pixel data from the aforementioned methods will be written + * starting at the offset specified by + * getDestinationOffset. + * + *

If destination is null, a + * newly-created BufferedImage will be returned by + * those methods. + * + *

At the time of reading, the image is checked to verify that + * its ColorModel and SampleModel + * correspond to one of the ImageTypeSpecifiers + * returned from the ImageReader's + * getImageTypes method. If it does not, the reader + * will throw an IIOException. + * + * @param destination the BufferedImage to be written to, or + * null. + * + * @see #getDestination + */ + public void setDestination(BufferedImage destination) { + this.destination = destination; + } + + /** + * Returns the BufferedImage currently set by the + * setDestination method, or null + * if none is set. + * + * @return the BufferedImage to be written to. + * + * @see #setDestination + */ + public BufferedImage getDestination() { + return destination; + } + + /** + * Sets the indices of the destination bands where data + * will be placed. Duplicate indices are not allowed. + * + *

A null value indicates that all destination + * bands will be used. + * + *

Choosing a destination band subset will not affect the + * number of bands in the output image of a read if no destination + * image is specified; the created destination image will still + * have the same number of bands as if this method had never been + * called. If a different number of bands in the destination + * image is desired, an image must be supplied using the + * ImageReadParam.setDestination method. + * + *

At the time of reading or writing, an + * IllegalArgumentException will be thrown by the + * reader or writer if a value larger than the largest destination + * band index has been specified, or if the number of source bands + * and destination bands to be used differ. The + * ImageReader.checkReadParamBandSettings method may + * be used to automate this test. + * + * @param destinationBands an array of integer band indices to be + * used. + * + * @exception IllegalArgumentException if destinationBands + * contains a negative or duplicate value. + * + * @see #getDestinationBands + * @see #getSourceBands + * @see ImageReader#checkReadParamBandSettings + */ + public void setDestinationBands(int[] destinationBands) { + if (destinationBands == null) { + this.destinationBands = null; + } else { + int numBands = destinationBands.length; + for (int i = 0; i < numBands; i++) { + int band = destinationBands[i]; + if (band < 0) { + throw new IllegalArgumentException("Band value < 0!"); + } + for (int j = i + 1; j < numBands; j++) { + if (band == destinationBands[j]) { + throw new IllegalArgumentException("Duplicate band value!"); + } + } + } + this.destinationBands = (int[])destinationBands.clone(); + } + } + + /** + * Returns the set of band indices where data will be placed. + * If no value has been set, null is returned to + * indicate that all destination bands will be used. + * + * @return the indices of the destination bands to be used, + * or null. + * + * @see #setDestinationBands + */ + public int[] getDestinationBands() { + if (destinationBands == null) { + return null; + } else { + return (int[])(destinationBands.clone()); + } + } + + /** + * Returns true if this reader allows the source + * image to be rendered at an arbitrary size as part of the + * decoding process, by means of the + * setSourceRenderSize method. If this method + * returns false, calls to + * setSourceRenderSize will throw an + * UnsupportedOperationException. + * + * @return true if setting source rendering size is + * supported. + * + * @see #setSourceRenderSize + */ + public boolean canSetSourceRenderSize() { + return canSetSourceRenderSize; + } + + /** + * If the image is able to be rendered at an arbitrary size, sets + * the source width and height to the supplied values. Note that + * the values returned from the getWidth and + * getHeight methods on ImageReader are + * not affected by this method; they will continue to return the + * default size for the image. Similarly, if the image is also + * tiled the tile width and height are given in terms of the default + * size. + * + *

Typically, the width and height should be chosen such that + * the ratio of width to height closely approximates the aspect + * ratio of the image, as returned from + * ImageReader.getAspectRatio. + * + *

If this plug-in does not allow the rendering size to be + * set, an UnsupportedOperationException will be + * thrown. + * + *

To remove the render size setting, pass in a value of + * null for size. + * + * @param size a Dimension indicating the desired + * width and height. + * + * @exception IllegalArgumentException if either the width or the + * height is negative or 0. + * @exception UnsupportedOperationException if image resizing + * is not supported by this plug-in. + * + * @see #getSourceRenderSize + * @see ImageReader#getWidth + * @see ImageReader#getHeight + * @see ImageReader#getAspectRatio + */ + public void setSourceRenderSize(Dimension size) + throws UnsupportedOperationException { + if (!canSetSourceRenderSize()) { + throw new UnsupportedOperationException + ("Can't set source render size!"); + } + + if (size == null) { + this.sourceRenderSize = null; + } else { + if (size.width <= 0 || size.height <= 0) { + throw new IllegalArgumentException("width or height <= 0!"); + } + this.sourceRenderSize = (Dimension)size.clone(); + } + } + + /** + * Returns the width and height of the source image as it + * will be rendered during decoding, if they have been set via the + * setSourceRenderSize method. A + * nullvalue indicates that no setting has been made. + * + * @return the rendered width and height of the source image + * as a Dimension. + * + * @see #setSourceRenderSize + */ + public Dimension getSourceRenderSize() { + return (sourceRenderSize == null) ? + null : (Dimension)sourceRenderSize.clone(); + } + + /** + * Sets the range of progressive passes that will be decoded. + * Passes outside of this range will be ignored. + * + *

A progressive pass is a re-encoding of the entire image, + * generally at progressively higher effective resolutions, but + * requiring greater transmission bandwidth. The most common use + * of progressive encoding is found in the JPEG format, where + * successive passes include more detailed representations of the + * high-frequency image content. + * + *

The actual number of passes to be decoded is determined + * during decoding, based on the number of actual passes available + * in the stream. Thus if minPass + numPasses - 1 is + * larger than the index of the last available passes, decoding + * will end with that pass. + * + *

A value of numPasses of + * Integer.MAX_VALUE indicates that all passes from + * minPass forward should be read. Otherwise, the + * index of the last pass (i.e., minPass + numPasses + * - 1) must not exceed Integer.MAX_VALUE. + * + *

There is no unsetSourceProgressivePasses + * method; the same effect may be obtained by calling + * setSourceProgressivePasses(0, Integer.MAX_VALUE). + * + * @param minPass the index of the first pass to be decoded. + * @param numPasses the maximum number of passes to be decoded. + * + * @exception IllegalArgumentException if minPass is + * negative, numPasses is negative or 0, or + * numPasses is smaller than + * Integer.MAX_VALUE but minPass + + * numPasses - 1 is greater than + * INTEGER.MAX_VALUE. + * + * @see #getSourceMinProgressivePass + * @see #getSourceMaxProgressivePass + */ + public void setSourceProgressivePasses(int minPass, int numPasses) { + if (minPass < 0) { + throw new IllegalArgumentException("minPass < 0!"); + } + if (numPasses <= 0) { + throw new IllegalArgumentException("numPasses <= 0!"); + } + if ((numPasses != Integer.MAX_VALUE) && + (((minPass + numPasses - 1) & 0x80000000) != 0)) { + throw new IllegalArgumentException + ("minPass + numPasses - 1 > INTEGER.MAX_VALUE!"); + } + + this.minProgressivePass = minPass; + this.numProgressivePasses = numPasses; + } + + /** + * Returns the index of the first progressive pass that will be + * decoded. If no value has been set, 0 will be returned (which is + * the correct value). + * + * @return the index of the first pass that will be decoded. + * + * @see #setSourceProgressivePasses + * @see #getSourceNumProgressivePasses + */ + public int getSourceMinProgressivePass() { + return minProgressivePass; + } + + /** + * If getSourceNumProgressivePasses is equal to + * Integer.MAX_VALUE, returns + * Integer.MAX_VALUE. Otherwise, returns + * getSourceMinProgressivePass() + + * getSourceNumProgressivePasses() - 1. + * + * @return the index of the last pass to be read, or + * Integer.MAX_VALUE. + */ + public int getSourceMaxProgressivePass() { + if (numProgressivePasses == Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else { + return minProgressivePass + numProgressivePasses - 1; + } + } + + /** + * Returns the number of the progressive passes that will be + * decoded. If no value has been set, + * Integer.MAX_VALUE will be returned (which is the + * correct value). + * + * @return the number of the passes that will be decoded. + * + * @see #setSourceProgressivePasses + * @see #getSourceMinProgressivePass + */ + public int getSourceNumProgressivePasses() { + return numProgressivePasses; + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/ImageReader.java b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageReader.java new file mode 100644 index 000000000..366a63d1e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageReader.java @@ -0,0 +1,2894 @@ +/* + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.event.IIOReadWarningListener; +import javax.imageio.event.IIOReadProgressListener; +import javax.imageio.event.IIOReadUpdateListener; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.stream.ImageInputStream; + +/** + * An abstract superclass for parsing and decoding of images. This + * class must be subclassed by classes that read in images in the + * context of the Java Image I/O framework. + * + *

ImageReader objects are normally instantiated by + * the service provider interface (SPI) class for the specific format. + * Service provider classes (e.g., instances of + * ImageReaderSpi) are registered with the + * IIORegistry, which uses them for format recognition + * and presentation of available format readers and writers. + * + *

When an input source is set (using the setInput + * method), it may be marked as "seek forward only". This setting + * means that images contained within the input source will only be + * read in order, possibly allowing the reader to avoid caching + * portions of the input containing data associated with images that + * have been read previously. + * + * @see ImageWriter + * @see javax.imageio.spi.IIORegistry + * @see javax.imageio.spi.ImageReaderSpi + * + */ +public abstract class ImageReader { + + /** + * The ImageReaderSpi that instantiated this object, + * or null if its identity is not known or none + * exists. By default it is initialized to null. + */ + protected ImageReaderSpi originatingProvider; + + /** + * The ImageInputStream or other + * Object by setInput and retrieved + * by getInput. By default it is initialized to + * null. + */ + protected Object input = null; + + /** + * true if the current input source has been marked + * as allowing only forward seeking by setInput. By + * default, the value is false. + * + * @see #minIndex + * @see #setInput + */ + protected boolean seekForwardOnly = false; + + /** + * true if the current input source has been marked + * as allowing metadata to be ignored by setInput. + * By default, the value is false. + * + * @see #setInput + */ + protected boolean ignoreMetadata = false; + + /** + * The smallest valid index for reading, initially 0. When + * seekForwardOnly is true, various methods + * may throw an IndexOutOfBoundsException on an + * attempt to access data associate with an image having a lower + * index. + * + * @see #seekForwardOnly + * @see #setInput + */ + protected int minIndex = 0; + + /** + * An array of Locales which may be used to localize + * warning messages, or null if localization is not + * supported. + */ + protected Locale[] availableLocales = null; + + /** + * The current Locale to be used for localization, or + * null if none has been set. + */ + protected Locale locale = null; + + /** + * A List of currently registered + * IIOReadWarningListeners, initialized by default to + * null, which is synonymous with an empty + * List. + */ + protected List warningListeners = null; + + /** + * A List of the Locales associated with + * each currently registered IIOReadWarningListener, + * initialized by default to null, which is + * synonymous with an empty List. + */ + protected List warningLocales = null; + + /** + * A List of currently registered + * IIOReadProgressListeners, initialized by default + * to null, which is synonymous with an empty + * List. + */ + protected List progressListeners = null; + + /** + * A List of currently registered + * IIOReadUpdateListeners, initialized by default to + * null, which is synonymous with an empty + * List. + */ + protected List updateListeners = null; + + /** + * If true, the current read operation should be + * aborted. + */ + private boolean abortFlag = false; + + /** + * Constructs an ImageReader and sets its + * originatingProvider field to the supplied value. + * + *

Subclasses that make use of extensions should provide a + * constructor with signature (ImageReaderSpi, + * Object) in order to retrieve the extension object. If + * the extension object is unsuitable, an + * IllegalArgumentException should be thrown. + * + * @param originatingProvider the ImageReaderSpi that is + * invoking this constructor, or null. + */ + protected ImageReader(ImageReaderSpi originatingProvider) { + this.originatingProvider = originatingProvider; + } + + /** + * Returns a String identifying the format of the + * input source. + * + *

The default implementation returns + * originatingProvider.getFormatNames()[0]. + * Implementations that may not have an originating service + * provider, or which desire a different naming policy should + * override this method. + * + * @exception IOException if an error occurs reading the + * information from the input source. + * + * @return the format name, as a String. + */ + public String getFormatName() throws IOException { + return originatingProvider.getFormatNames()[0]; + } + + /** + * Returns the ImageReaderSpi that was passed in on + * the constructor. Note that this value may be null. + * + * @return an ImageReaderSpi, or null. + * + * @see ImageReaderSpi + */ + public ImageReaderSpi getOriginatingProvider() { + return originatingProvider; + } + + /** + * Sets the input source to use to the given + * ImageInputStream or other Object. + * The input source must be set before any of the query or read + * methods are used. If input is null, + * any currently set input source will be removed. In any case, + * the value of minIndex will be initialized to 0. + * + *

The seekForwardOnly parameter controls whether + * the value returned by getMinIndex will be + * increased as each image (or thumbnail, or image metadata) is + * read. If seekForwardOnly is true, then a call to + * read(index) will throw an + * IndexOutOfBoundsException if {@code index < this.minIndex}; + * otherwise, the value of + * minIndex will be set to index. If + * seekForwardOnly is false, the value of + * minIndex will remain 0 regardless of any read + * operations. + * + *

The ignoreMetadata parameter, if set to + * true, allows the reader to disregard any metadata + * encountered during the read. Subsequent calls to the + * getStreamMetadata and + * getImageMetadata methods may return + * null, and an IIOImage returned from + * readAll may return null from their + * getMetadata method. Setting this parameter may + * allow the reader to work more efficiently. The reader may + * choose to disregard this setting and return metadata normally. + * + *

Subclasses should take care to remove any cached + * information based on the previous stream, such as header + * information or partially decoded image data. + * + *

Use of a general Object other than an + * ImageInputStream is intended for readers that + * interact directly with a capture device or imaging protocol. + * The set of legal classes is advertised by the reader's service + * provider's getInputTypes method; most readers + * will return a single-element array containing only + * ImageInputStream.class to indicate that they + * accept only an ImageInputStream. + * + *

The default implementation checks the input + * argument against the list returned by + * originatingProvider.getInputTypes() and fails + * if the argument is not an instance of one of the classes + * in the list. If the originating provider is set to + * null, the input is accepted only if it is an + * ImageInputStream. + * + * @param input the ImageInputStream or other + * Object to use for future decoding. + * @param seekForwardOnly if true, images and metadata + * may only be read in ascending order from this input source. + * @param ignoreMetadata if true, metadata + * may be ignored during reads. + * + * @exception IllegalArgumentException if input is + * not an instance of one of the classes returned by the + * originating service provider's getInputTypes + * method, or is not an ImageInputStream. + * + * @see ImageInputStream + * @see #getInput + * @see javax.imageio.spi.ImageReaderSpi#getInputTypes + */ + public void setInput(Object input, + boolean seekForwardOnly, + boolean ignoreMetadata) { + if (input != null) { + boolean found = false; + if (originatingProvider != null) { + Class[] classes = originatingProvider.getInputTypes(); + for (int i = 0; i < classes.length; i++) { + if (classes[i].isInstance(input)) { + found = true; + break; + } + } + } else { + if (input instanceof ImageInputStream) { + found = true; + } + } + if (!found) { + throw new IllegalArgumentException("Incorrect input type!"); + } + + this.seekForwardOnly = seekForwardOnly; + this.ignoreMetadata = ignoreMetadata; + this.minIndex = 0; + } + + this.input = input; + } + + /** + * Sets the input source to use to the given + * ImageInputStream or other Object. + * The input source must be set before any of the query or read + * methods are used. If input is null, + * any currently set input source will be removed. In any case, + * the value of minIndex will be initialized to 0. + * + *

The seekForwardOnly parameter controls whether + * the value returned by getMinIndex will be + * increased as each image (or thumbnail, or image metadata) is + * read. If seekForwardOnly is true, then a call to + * read(index) will throw an + * IndexOutOfBoundsException if {@code index < this.minIndex}; + * otherwise, the value of + * minIndex will be set to index. If + * seekForwardOnly is false, the value of + * minIndex will remain 0 regardless of any read + * operations. + * + *

This method is equivalent to setInput(input, + * seekForwardOnly, false). + * + * @param input the ImageInputStream or other + * Object to use for future decoding. + * @param seekForwardOnly if true, images and metadata + * may only be read in ascending order from this input source. + * + * @exception IllegalArgumentException if input is + * not an instance of one of the classes returned by the + * originating service provider's getInputTypes + * method, or is not an ImageInputStream. + * + * @see #getInput + */ + public void setInput(Object input, + boolean seekForwardOnly) { + setInput(input, seekForwardOnly, false); + } + + /** + * Sets the input source to use to the given + * ImageInputStream or other Object. + * The input source must be set before any of the query or read + * methods are used. If input is null, + * any currently set input source will be removed. In any case, + * the value of minIndex will be initialized to 0. + * + *

This method is equivalent to setInput(input, false, + * false). + * + * @param input the ImageInputStream or other + * Object to use for future decoding. + * + * @exception IllegalArgumentException if input is + * not an instance of one of the classes returned by the + * originating service provider's getInputTypes + * method, or is not an ImageInputStream. + * + * @see #getInput + */ + public void setInput(Object input) { + setInput(input, false, false); + } + + /** + * Returns the ImageInputStream or other + * Object previously set as the input source. If the + * input source has not been set, null is returned. + * + * @return the Object that will be used for future + * decoding, or null. + * + * @see ImageInputStream + * @see #setInput + */ + public Object getInput() { + return input; + } + + /** + * Returns true if the current input source has been + * marked as seek forward only by passing true as the + * seekForwardOnly argument to the + * setInput method. + * + * @return true if the input source is seek forward + * only. + * + * @see #setInput + */ + public boolean isSeekForwardOnly() { + return seekForwardOnly; + } + + /** + * Returns true if the current input source has been + * marked as allowing metadata to be ignored by passing + * true as the ignoreMetadata argument + * to the setInput method. + * + * @return true if the metadata may be ignored. + * + * @see #setInput + */ + public boolean isIgnoringMetadata() { + return ignoreMetadata; + } + + /** + * Returns the lowest valid index for reading an image, thumbnail, + * or image metadata. If seekForwardOnly() is + * false, this value will typically remain 0, + * indicating that random access is possible. Otherwise, it will + * contain the value of the most recently accessed index, and + * increase in a monotonic fashion. + * + * @return the minimum legal index for reading. + */ + public int getMinIndex() { + return minIndex; + } + + // Localization + + /** + * Returns an array of Locales that may be used to + * localize warning listeners and compression settings. A return + * value of null indicates that localization is not + * supported. + * + *

The default implementation returns a clone of the + * availableLocales instance variable if it is + * non-null, or else returns null. + * + * @return an array of Locales that may be used as + * arguments to setLocale, or null. + */ + public Locale[] getAvailableLocales() { + if (availableLocales == null) { + return null; + } else { + return (Locale[])availableLocales.clone(); + } + } + + /** + * Sets the current Locale of this + * ImageReader to the given value. A value of + * null removes any previous setting, and indicates + * that the reader should localize as it sees fit. + * + * @param locale the desired Locale, or + * null. + * + * @exception IllegalArgumentException if locale is + * non-null but is not one of the values returned by + * getAvailableLocales. + * + * @see #getLocale + */ + public void setLocale(Locale locale) { + if (locale != null) { + Locale[] locales = getAvailableLocales(); + boolean found = false; + if (locales != null) { + for (int i = 0; i < locales.length; i++) { + if (locale.equals(locales[i])) { + found = true; + break; + } + } + } + if (!found) { + throw new IllegalArgumentException("Invalid locale!"); + } + } + this.locale = locale; + } + + /** + * Returns the currently set Locale, or + * null if none has been set. + * + * @return the current Locale, or null. + * + * @see #setLocale + */ + public Locale getLocale() { + return locale; + } + + // Image queries + + /** + * Returns the number of images, not including thumbnails, available + * from the current input source. + * + *

Note that some image formats (such as animated GIF) do not + * specify how many images are present in the stream. Thus + * determining the number of images will require the entire stream + * to be scanned and may require memory for buffering. If images + * are to be processed in order, it may be more efficient to + * simply call read with increasing indices until an + * IndexOutOfBoundsException is thrown to indicate + * that no more images are available. The + * allowSearch parameter may be set to + * false to indicate that an exhaustive search is not + * desired; the return value will be -1 to indicate + * that a search is necessary. If the input has been specified + * with seekForwardOnly set to true, + * this method throws an IllegalStateException if + * allowSearch is set to true. + * + * @param allowSearch if true, the true number of + * images will be returned even if a search is required. If + * false, the reader may return -1 + * without performing the search. + * + * @return the number of images, as an int, or + * -1 if allowSearch is + * false and a search would be required. + * + * @exception IllegalStateException if the input source has not been set, + * or if the input has been specified with seekForwardOnly + * set to true. + * @exception IOException if an error occurs reading the + * information from the input source. + * + * @see #setInput + */ + public abstract int getNumImages(boolean allowSearch) throws IOException; + + /** + * Returns the width in pixels of the given image within the input + * source. + * + *

If the image can be rendered to a user-specified size, then + * this method returns the default width. + * + * @param imageIndex the index of the image to be queried. + * + * @return the width of the image, as an int. + * + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs reading the width + * information from the input source. + */ + public abstract int getWidth(int imageIndex) throws IOException; + + /** + * Returns the height in pixels of the given image within the + * input source. + * + *

If the image can be rendered to a user-specified size, then + * this method returns the default height. + * + * @param imageIndex the index of the image to be queried. + * + * @return the height of the image, as an int. + * + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs reading the height + * information from the input source. + */ + public abstract int getHeight(int imageIndex) throws IOException; + + /** + * Returns true if the storage format of the given + * image places no inherent impediment on random access to pixels. + * For most compressed formats, such as JPEG, this method should + * return false, as a large section of the image in + * addition to the region of interest may need to be decoded. + * + *

This is merely a hint for programs that wish to be + * efficient; all readers must be able to read arbitrary regions + * as specified in an ImageReadParam. + * + *

Note that formats that return false from + * this method may nonetheless allow tiling (e.g. Restart + * Markers in JPEG), and random access will likely be reasonably + * efficient on tiles. See {@link #isImageTiled isImageTiled}. + * + *

A reader for which all images are guaranteed to support + * easy random access, or are guaranteed not to support easy + * random access, may return true or + * false respectively without accessing any image + * data. In such cases, it is not necessary to throw an exception + * even if no input source has been set or the image index is out + * of bounds. + * + *

The default implementation returns false. + * + * @param imageIndex the index of the image to be queried. + * + * @return true if reading a region of interest of + * the given image is likely to be efficient. + * + * @exception IllegalStateException if an input source is required + * to determine the return value, but none has been set. + * @exception IndexOutOfBoundsException if an image must be + * accessed to determine the return value, but the supplied index + * is out of bounds. + * @exception IOException if an error occurs during reading. + */ + public boolean isRandomAccessEasy(int imageIndex) throws IOException { + return false; + } + + /** + * Returns the aspect ratio of the given image (that is, its width + * divided by its height) as a float. For images + * that are inherently resizable, this method provides a way to + * determine the appropriate width given a desired height, or vice + * versa. For non-resizable images, the true width and height + * are used. + * + *

The default implementation simply returns + * (float)getWidth(imageIndex)/getHeight(imageIndex). + * + * @param imageIndex the index of the image to be queried. + * + * @return a float indicating the aspect ratio of the + * given image. + * + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs during reading. + */ + public float getAspectRatio(int imageIndex) throws IOException { + return (float)getWidth(imageIndex)/getHeight(imageIndex); + } + + /** + * Returns an ImageTypeSpecifier indicating the + * SampleModel and ColorModel which most + * closely represents the "raw" internal format of the image. For + * example, for a JPEG image the raw type might have a YCbCr color + * space even though the image would conventionally be transformed + * into an RGB color space prior to display. The returned value + * should also be included in the list of values returned by + * getImageTypes. + * + *

The default implementation simply returns the first entry + * from the list provided by getImageType. + * + * @param imageIndex the index of the image to be queried. + * + * @return an ImageTypeSpecifier. + * + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs reading the format + * information from the input source. + */ + public ImageTypeSpecifier getRawImageType(int imageIndex) + throws IOException { + return (ImageTypeSpecifier)getImageTypes(imageIndex).next(); + } + + /** + * Returns an Iterator containing possible image + * types to which the given image may be decoded, in the form of + * ImageTypeSpecifierss. At least one legal image + * type will be returned. + * + *

The first element of the iterator should be the most + * "natural" type for decoding the image with as little loss as + * possible. For example, for a JPEG image the first entry should + * be an RGB image, even though the image data is stored + * internally in a YCbCr color space. + * + * @param imageIndex the index of the image to be + * retrieved. + * + * @return an Iterator containing at least one + * ImageTypeSpecifier representing suggested image + * types for decoding the current given image. + * + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs reading the format + * information from the input source. + * + * @see ImageReadParam#setDestination(BufferedImage) + * @see ImageReadParam#setDestinationType(ImageTypeSpecifier) + */ + public abstract Iterator + getImageTypes(int imageIndex) throws IOException; + + /** + * Returns a default ImageReadParam object + * appropriate for this format. All subclasses should define a + * set of default values for all parameters and return them with + * this call. This method may be called before the input source + * is set. + * + *

The default implementation constructs and returns a new + * ImageReadParam object that does not allow source + * scaling (i.e., it returns new + * ImageReadParam(). + * + * @return an ImageReadParam object which may be used + * to control the decoding process using a set of default settings. + */ + public ImageReadParam getDefaultReadParam() { + return new ImageReadParam(); + } + + /** + * Returns an IIOMetadata object representing the + * metadata associated with the input source as a whole (i.e., not + * associated with any particular image), or null if + * the reader does not support reading metadata, is set to ignore + * metadata, or if no metadata is available. + * + * @return an IIOMetadata object, or null. + * + * @exception IOException if an error occurs during reading. + */ + public abstract IIOMetadata getStreamMetadata() throws IOException; + + /** + * Returns an IIOMetadata object representing the + * metadata associated with the input source as a whole (i.e., + * not associated with any particular image). If no such data + * exists, null is returned. + * + *

The resulting metadata object is only responsible for + * returning documents in the format named by + * formatName. Within any documents that are + * returned, only nodes whose names are members of + * nodeNames are required to be returned. In this + * way, the amount of metadata processing done by the reader may + * be kept to a minimum, based on what information is actually + * needed. + * + *

If formatName is not the name of a supported + * metadata format, null is returned. + * + *

In all cases, it is legal to return a more capable metadata + * object than strictly necessary. The format name and node names + * are merely hints that may be used to reduce the reader's + * workload. + * + *

The default implementation simply returns the result of + * calling getStreamMetadata(), after checking that + * the format name is supported. If it is not, + * null is returned. + * + * @param formatName a metadata format name that may be used to retrieve + * a document from the returned IIOMetadata object. + * @param nodeNames a Set containing the names of + * nodes that may be contained in a retrieved document. + * + * @return an IIOMetadata object, or null. + * + * @exception IllegalArgumentException if formatName + * is null. + * @exception IllegalArgumentException if nodeNames + * is null. + * @exception IOException if an error occurs during reading. + */ + public IIOMetadata getStreamMetadata(String formatName, + Set nodeNames) + throws IOException + { + return getMetadata(formatName, nodeNames, true, 0); + } + + private IIOMetadata getMetadata(String formatName, + Set nodeNames, + boolean wantStream, + int imageIndex) throws IOException { + if (formatName == null) { + throw new IllegalArgumentException("formatName == null!"); + } + if (nodeNames == null) { + throw new IllegalArgumentException("nodeNames == null!"); + } + IIOMetadata metadata = + wantStream + ? getStreamMetadata() + : getImageMetadata(imageIndex); + if (metadata != null) { + if (metadata.isStandardMetadataFormatSupported() && + formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return metadata; + } + String nativeName = metadata.getNativeMetadataFormatName(); + if (nativeName != null && formatName.equals(nativeName)) { + return metadata; + } + String[] extraNames = metadata.getExtraMetadataFormatNames(); + if (extraNames != null) { + for (int i = 0; i < extraNames.length; i++) { + if (formatName.equals(extraNames[i])) { + return metadata; + } + } + } + } + return null; + } + + /** + * Returns an IIOMetadata object containing metadata + * associated with the given image, or null if the + * reader does not support reading metadata, is set to ignore + * metadata, or if no metadata is available. + * + * @param imageIndex the index of the image whose metadata is to + * be retrieved. + * + * @return an IIOMetadata object, or + * null. + * + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs during reading. + */ + public abstract IIOMetadata getImageMetadata(int imageIndex) + throws IOException; + + /** + * Returns an IIOMetadata object representing the + * metadata associated with the given image, or null + * if the reader does not support reading metadata or none + * is available. + * + *

The resulting metadata object is only responsible for + * returning documents in the format named by + * formatName. Within any documents that are + * returned, only nodes whose names are members of + * nodeNames are required to be returned. In this + * way, the amount of metadata processing done by the reader may + * be kept to a minimum, based on what information is actually + * needed. + * + *

If formatName is not the name of a supported + * metadata format, null may be returned. + * + *

In all cases, it is legal to return a more capable metadata + * object than strictly necessary. The format name and node names + * are merely hints that may be used to reduce the reader's + * workload. + * + *

The default implementation simply returns the result of + * calling getImageMetadata(imageIndex), after + * checking that the format name is supported. If it is not, + * null is returned. + * + * @param imageIndex the index of the image whose metadata is to + * be retrieved. + * @param formatName a metadata format name that may be used to retrieve + * a document from the returned IIOMetadata object. + * @param nodeNames a Set containing the names of + * nodes that may be contained in a retrieved document. + * + * @return an IIOMetadata object, or null. + * + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IllegalArgumentException if formatName + * is null. + * @exception IllegalArgumentException if nodeNames + * is null. + * @exception IOException if an error occurs during reading. + */ + public IIOMetadata getImageMetadata(int imageIndex, + String formatName, + Set nodeNames) + throws IOException { + return getMetadata(formatName, nodeNames, false, imageIndex); + } + + /** + * Reads the image indexed by imageIndex and returns + * it as a complete BufferedImage, using a default + * ImageReadParam. This is a convenience method + * that calls read(imageIndex, null). + * + *

The image returned will be formatted according to the first + * ImageTypeSpecifier returned from + * getImageTypes. + * + *

Any registered IIOReadProgressListener objects + * will be notified by calling their imageStarted + * method, followed by calls to their imageProgress + * method as the read progresses. Finally their + * imageComplete method will be called. + * IIOReadUpdateListener objects may be updated at + * other times during the read as pixels are decoded. Finally, + * IIOReadWarningListener objects will receive + * notification of any non-fatal warnings that occur during + * decoding. + * + * @param imageIndex the index of the image to be retrieved. + * + * @return the desired portion of the image as a + * BufferedImage. + * + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs during reading. + */ + public BufferedImage read(int imageIndex) throws IOException { + return read(imageIndex, null); + } + + /** + * Reads the image indexed by imageIndex and returns + * it as a complete BufferedImage, using a supplied + * ImageReadParam. + * + *

The actual BufferedImage returned will be + * chosen using the algorithm defined by the + * getDestination method. + * + *

Any registered IIOReadProgressListener objects + * will be notified by calling their imageStarted + * method, followed by calls to their imageProgress + * method as the read progresses. Finally their + * imageComplete method will be called. + * IIOReadUpdateListener objects may be updated at + * other times during the read as pixels are decoded. Finally, + * IIOReadWarningListener objects will receive + * notification of any non-fatal warnings that occur during + * decoding. + * + *

The set of source bands to be read and destination bands to + * be written is determined by calling getSourceBands + * and getDestinationBands on the supplied + * ImageReadParam. If the lengths of the arrays + * returned by these methods differ, the set of source bands + * contains an index larger that the largest available source + * index, or the set of destination bands contains an index larger + * than the largest legal destination index, an + * IllegalArgumentException is thrown. + * + *

If the supplied ImageReadParam contains + * optional setting values not supported by this reader (e.g. + * source render size or any format-specific settings), they will + * be ignored. + * + * @param imageIndex the index of the image to be retrieved. + * @param param an ImageReadParam used to control + * the reading process, or null. + * + * @return the desired portion of the image as a + * BufferedImage. + * + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IllegalArgumentException if the set of source and + * destination bands specified by + * param.getSourceBands and + * param.getDestinationBands differ in length or + * include indices that are out of bounds. + * @exception IllegalArgumentException if the resulting image would + * have a width or height less than 1. + * @exception IOException if an error occurs during reading. + */ + public abstract BufferedImage read(int imageIndex, ImageReadParam param) + throws IOException; + + /** + * Reads the image indexed by imageIndex and returns + * an IIOImage containing the image, thumbnails, and + * associated image metadata, using a supplied + * ImageReadParam. + * + *

The actual BufferedImage referenced by the + * returned IIOImage will be chosen using the + * algorithm defined by the getDestination method. + * + *

Any registered IIOReadProgressListener objects + * will be notified by calling their imageStarted + * method, followed by calls to their imageProgress + * method as the read progresses. Finally their + * imageComplete method will be called. + * IIOReadUpdateListener objects may be updated at + * other times during the read as pixels are decoded. Finally, + * IIOReadWarningListener objects will receive + * notification of any non-fatal warnings that occur during + * decoding. + * + *

The set of source bands to be read and destination bands to + * be written is determined by calling getSourceBands + * and getDestinationBands on the supplied + * ImageReadParam. If the lengths of the arrays + * returned by these methods differ, the set of source bands + * contains an index larger that the largest available source + * index, or the set of destination bands contains an index larger + * than the largest legal destination index, an + * IllegalArgumentException is thrown. + * + *

Thumbnails will be returned in their entirety regardless of + * the region settings. + * + *

If the supplied ImageReadParam contains + * optional setting values not supported by this reader (e.g. + * source render size or any format-specific settings), those + * values will be ignored. + * + * @param imageIndex the index of the image to be retrieved. + * @param param an ImageReadParam used to control + * the reading process, or null. + * + * @return an IIOImage containing the desired portion + * of the image, a set of thumbnails, and associated image + * metadata. + * + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IllegalArgumentException if the set of source and + * destination bands specified by + * param.getSourceBands and + * param.getDestinationBands differ in length or + * include indices that are out of bounds. + * @exception IllegalArgumentException if the resulting image + * would have a width or height less than 1. + * @exception IOException if an error occurs during reading. + */ + public IIOImage readAll(int imageIndex, ImageReadParam param) + throws IOException { + if (imageIndex < getMinIndex()) { + throw new IndexOutOfBoundsException("imageIndex < getMinIndex()!"); + } + + BufferedImage im = read(imageIndex, param); + + ArrayList thumbnails = null; + int numThumbnails = getNumThumbnails(imageIndex); + if (numThumbnails > 0) { + thumbnails = new ArrayList(); + for (int j = 0; j < numThumbnails; j++) { + thumbnails.add(readThumbnail(imageIndex, j)); + } + } + + IIOMetadata metadata = getImageMetadata(imageIndex); + return new IIOImage(im, thumbnails, metadata); + } + + /** + * Returns an Iterator containing all the images, + * thumbnails, and metadata, starting at the index given by + * getMinIndex, from the input source in the form of + * IIOImage objects. An Iterator + * containing ImageReadParam objects is supplied; one + * element is consumed for each image read from the input source + * until no more images are available. If the read param + * Iterator runs out of elements, but there are still + * more images available from the input source, default read + * params are used for the remaining images. + * + *

If params is null, a default read + * param will be used for all images. + * + *

The actual BufferedImage referenced by the + * returned IIOImage will be chosen using the + * algorithm defined by the getDestination method. + * + *

Any registered IIOReadProgressListener objects + * will be notified by calling their sequenceStarted + * method once. Then, for each image decoded, there will be a + * call to imageStarted, followed by calls to + * imageProgress as the read progresses, and finally + * to imageComplete. The + * sequenceComplete method will be called after the + * last image has been decoded. + * IIOReadUpdateListener objects may be updated at + * other times during the read as pixels are decoded. Finally, + * IIOReadWarningListener objects will receive + * notification of any non-fatal warnings that occur during + * decoding. + * + *

The set of source bands to be read and destination bands to + * be written is determined by calling getSourceBands + * and getDestinationBands on the supplied + * ImageReadParam. If the lengths of the arrays + * returned by these methods differ, the set of source bands + * contains an index larger that the largest available source + * index, or the set of destination bands contains an index larger + * than the largest legal destination index, an + * IllegalArgumentException is thrown. + * + *

Thumbnails will be returned in their entirety regardless of the + * region settings. + * + *

If any of the supplied ImageReadParams contain + * optional setting values not supported by this reader (e.g. + * source render size or any format-specific settings), they will + * be ignored. + * + * @param params an Iterator containing + * ImageReadParam objects. + * + * @return an Iterator representing the + * contents of the input source as IIOImages. + * + * @exception IllegalStateException if the input source has not been + * set. + * @exception IllegalArgumentException if any + * non-null element of params is not an + * ImageReadParam. + * @exception IllegalArgumentException if the set of source and + * destination bands specified by + * param.getSourceBands and + * param.getDestinationBands differ in length or + * include indices that are out of bounds. + * @exception IllegalArgumentException if a resulting image would + * have a width or height less than 1. + * @exception IOException if an error occurs during reading. + * + * @see ImageReadParam + * @see IIOImage + */ + public Iterator + readAll(Iterator params) + throws IOException + { + List output = new ArrayList(); + + int imageIndex = getMinIndex(); + + // Inform IIOReadProgressListeners we're starting a sequence + processSequenceStarted(imageIndex); + + while (true) { + // Inform IIOReadProgressListeners and IIOReadUpdateListeners + // that we're starting a new image + + ImageReadParam param = null; + if (params != null && params.hasNext()) { + Object o = params.next(); + if (o != null) { + if (o instanceof ImageReadParam) { + param = (ImageReadParam)o; + } else { + throw new IllegalArgumentException + ("Non-ImageReadParam supplied as part of params!"); + } + } + } + + BufferedImage bi = null; + try { + bi = read(imageIndex, param); + } catch (IndexOutOfBoundsException e) { + break; + } + + ArrayList thumbnails = null; + int numThumbnails = getNumThumbnails(imageIndex); + if (numThumbnails > 0) { + thumbnails = new ArrayList(); + for (int j = 0; j < numThumbnails; j++) { + thumbnails.add(readThumbnail(imageIndex, j)); + } + } + + IIOMetadata metadata = getImageMetadata(imageIndex); + IIOImage im = new IIOImage(bi, thumbnails, metadata); + output.add(im); + + ++imageIndex; + } + + // Inform IIOReadProgressListeners we're ending a sequence + processSequenceComplete(); + + return output.iterator(); + } + + /** + * Returns true if this plug-in supports reading + * just a {@link java.awt.image.Raster Raster} of pixel data. + * If this method returns false, calls to + * {@link #readRaster readRaster} or {@link #readTileRaster readTileRaster} + * will throw an UnsupportedOperationException. + * + *

The default implementation returns false. + * + * @return true if this plug-in supports reading raw + * Rasters. + * + * @see #readRaster + * @see #readTileRaster + */ + public boolean canReadRaster() { + return false; + } + + /** + * Returns a new Raster object containing the raw pixel data + * from the image stream, without any color conversion applied. The + * application must determine how to interpret the pixel data by other + * means. Any destination or image-type parameters in the supplied + * ImageReadParam object are ignored, but all other + * parameters are used exactly as in the {@link #read read} + * method, except that any destination offset is used as a logical rather + * than a physical offset. The size of the returned Raster + * will always be that of the source region clipped to the actual image. + * Logical offsets in the stream itself are ignored. + * + *

This method allows formats that normally apply a color + * conversion, such as JPEG, and formats that do not normally have an + * associated colorspace, such as remote sensing or medical imaging data, + * to provide access to raw pixel data. + * + *

Any registered readUpdateListeners are ignored, as + * there is no BufferedImage, but all other listeners are + * called exactly as they are for the {@link #read read} method. + * + *

If {@link #canReadRaster canReadRaster()} returns + * false, this method throws an + * UnsupportedOperationException. + * + *

If the supplied ImageReadParam contains + * optional setting values not supported by this reader (e.g. + * source render size or any format-specific settings), they will + * be ignored. + * + *

The default implementation throws an + * UnsupportedOperationException. + * + * @param imageIndex the index of the image to be read. + * @param param an ImageReadParam used to control + * the reading process, or null. + * + * @return the desired portion of the image as a + * Raster. + * + * @exception UnsupportedOperationException if this plug-in does not + * support reading raw Rasters. + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs during reading. + * + * @see #canReadRaster + * @see #read + * @see java.awt.image.Raster + */ + public Raster readRaster(int imageIndex, ImageReadParam param) + throws IOException { + throw new UnsupportedOperationException("readRaster not supported!"); + } + + /** + * Returns true if the image is organized into + * tiles, that is, equal-sized non-overlapping rectangles. + * + *

A reader plug-in may choose whether or not to expose tiling + * that is present in the image as it is stored. It may even + * choose to advertise tiling when none is explicitly present. In + * general, tiling should only be advertised if there is some + * advantage (in speed or space) to accessing individual tiles. + * Regardless of whether the reader advertises tiling, it must be + * capable of reading an arbitrary rectangular region specified in + * an ImageReadParam. + * + *

A reader for which all images are guaranteed to be tiled, + * or are guaranteed not to be tiled, may return true + * or false respectively without accessing any image + * data. In such cases, it is not necessary to throw an exception + * even if no input source has been set or the image index is out + * of bounds. + * + *

The default implementation just returns false. + * + * @param imageIndex the index of the image to be queried. + * + * @return true if the image is tiled. + * + * @exception IllegalStateException if an input source is required + * to determine the return value, but none has been set. + * @exception IndexOutOfBoundsException if an image must be + * accessed to determine the return value, but the supplied index + * is out of bounds. + * @exception IOException if an error occurs during reading. + */ + public boolean isImageTiled(int imageIndex) throws IOException { + return false; + } + + /** + * Returns the width of a tile in the given image. + * + *

The default implementation simply returns + * getWidth(imageIndex), which is correct for + * non-tiled images. Readers that support tiling should override + * this method. + * + * @return the width of a tile. + * + * @param imageIndex the index of the image to be queried. + * + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs during reading. + */ + public int getTileWidth(int imageIndex) throws IOException { + return getWidth(imageIndex); + } + + /** + * Returns the height of a tile in the given image. + * + *

The default implementation simply returns + * getHeight(imageIndex), which is correct for + * non-tiled images. Readers that support tiling should override + * this method. + * + * @return the height of a tile. + * + * @param imageIndex the index of the image to be queried. + * + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IOException if an error occurs during reading. + */ + public int getTileHeight(int imageIndex) throws IOException { + return getHeight(imageIndex); + } + + /** + * Returns the X coordinate of the upper-left corner of tile (0, + * 0) in the given image. + * + *

A reader for which the tile grid X offset always has the + * same value (usually 0), may return the value without accessing + * any image data. In such cases, it is not necessary to throw an + * exception even if no input source has been set or the image + * index is out of bounds. + * + *

The default implementation simply returns 0, which is + * correct for non-tiled images and tiled images in most formats. + * Readers that support tiling with non-(0, 0) offsets should + * override this method. + * + * @return the X offset of the tile grid. + * + * @param imageIndex the index of the image to be queried. + * + * @exception IllegalStateException if an input source is required + * to determine the return value, but none has been set. + * @exception IndexOutOfBoundsException if an image must be + * accessed to determine the return value, but the supplied index + * is out of bounds. + * @exception IOException if an error occurs during reading. + */ + public int getTileGridXOffset(int imageIndex) throws IOException { + return 0; + } + + /** + * Returns the Y coordinate of the upper-left corner of tile (0, + * 0) in the given image. + * + *

A reader for which the tile grid Y offset always has the + * same value (usually 0), may return the value without accessing + * any image data. In such cases, it is not necessary to throw an + * exception even if no input source has been set or the image + * index is out of bounds. + * + *

The default implementation simply returns 0, which is + * correct for non-tiled images and tiled images in most formats. + * Readers that support tiling with non-(0, 0) offsets should + * override this method. + * + * @return the Y offset of the tile grid. + * + * @param imageIndex the index of the image to be queried. + * + * @exception IllegalStateException if an input source is required + * to determine the return value, but none has been set. + * @exception IndexOutOfBoundsException if an image must be + * accessed to determine the return value, but the supplied index + * is out of bounds. + * @exception IOException if an error occurs during reading. + */ + public int getTileGridYOffset(int imageIndex) throws IOException { + return 0; + } + + /** + * Reads the tile indicated by the tileX and + * tileY arguments, returning it as a + * BufferedImage. If the arguments are out of range, + * an IllegalArgumentException is thrown. If the + * image is not tiled, the values 0, 0 will return the entire + * image; any other values will cause an + * IllegalArgumentException to be thrown. + * + *

This method is merely a convenience equivalent to calling + * read(int, ImageReadParam) with a read param + * specifying a source region having offsets of + * tileX*getTileWidth(imageIndex), + * tileY*getTileHeight(imageIndex) and width and + * height of getTileWidth(imageIndex), + * getTileHeight(imageIndex); and subsampling + * factors of 1 and offsets of 0. To subsample a tile, call + * read with a read param specifying this region + * and different subsampling parameters. + * + *

The default implementation returns the entire image if + * tileX and tileY are 0, or throws + * an IllegalArgumentException otherwise. + * + * @param imageIndex the index of the image to be retrieved. + * @param tileX the column index (starting with 0) of the tile + * to be retrieved. + * @param tileY the row index (starting with 0) of the tile + * to be retrieved. + * + * @return the tile as a BufferedImage. + * + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if imageIndex + * is out of bounds. + * @exception IllegalArgumentException if the tile indices are + * out of bounds. + * @exception IOException if an error occurs during reading. + */ + public BufferedImage readTile(int imageIndex, + int tileX, int tileY) throws IOException { + if ((tileX != 0) || (tileY != 0)) { + throw new IllegalArgumentException("Invalid tile indices"); + } + return read(imageIndex); + } + + /** + * Returns a new Raster object containing the raw + * pixel data from the tile, without any color conversion applied. + * The application must determine how to interpret the pixel data by other + * means. + * + *

If {@link #canReadRaster canReadRaster()} returns + * false, this method throws an + * UnsupportedOperationException. + * + *

The default implementation checks if reading + * Rasters is supported, and if so calls {@link + * #readRaster readRaster(imageIndex, null)} if + * tileX and tileY are 0, or throws an + * IllegalArgumentException otherwise. + * + * @param imageIndex the index of the image to be retrieved. + * @param tileX the column index (starting with 0) of the tile + * to be retrieved. + * @param tileY the row index (starting with 0) of the tile + * to be retrieved. + * + * @return the tile as a Raster. + * + * @exception UnsupportedOperationException if this plug-in does not + * support reading raw Rasters. + * @exception IllegalArgumentException if the tile indices are + * out of bounds. + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if imageIndex + * is out of bounds. + * @exception IOException if an error occurs during reading. + * + * @see #readTile + * @see #readRaster + * @see java.awt.image.Raster + */ + public Raster readTileRaster(int imageIndex, + int tileX, int tileY) throws IOException { + if (!canReadRaster()) { + throw new UnsupportedOperationException + ("readTileRaster not supported!"); + } + if ((tileX != 0) || (tileY != 0)) { + throw new IllegalArgumentException("Invalid tile indices"); + } + return readRaster(imageIndex, null); + } + + // RenderedImages + + /** + * Returns a RenderedImage object that contains the + * contents of the image indexed by imageIndex. By + * default, the returned image is simply the + * BufferedImage returned by read(imageIndex, + * param). + * + *

The semantics of this method may differ from those of the + * other read methods in several ways. First, any + * destination image and/or image type set in the + * ImageReadParam may be ignored. Second, the usual + * listener calls are not guaranteed to be made, or to be + * meaningful if they are. This is because the returned image may + * not be fully populated with pixel data at the time it is + * returned, or indeed at any time. + * + *

If the supplied ImageReadParam contains + * optional setting values not supported by this reader (e.g. + * source render size or any format-specific settings), they will + * be ignored. + * + *

The default implementation just calls + * {@link #read read(imageIndex, param)}. + * + * @param imageIndex the index of the image to be retrieved. + * @param param an ImageReadParam used to control + * the reading process, or null. + * + * @return a RenderedImage object providing a view of + * the image. + * + * @exception IllegalStateException if the input source has not been + * set. + * @exception IndexOutOfBoundsException if the supplied index is + * out of bounds. + * @exception IllegalArgumentException if the set of source and + * destination bands specified by + * param.getSourceBands and + * param.getDestinationBands differ in length or + * include indices that are out of bounds. + * @exception IllegalArgumentException if the resulting image + * would have a width or height less than 1. + * @exception IOException if an error occurs during reading. + */ + public RenderedImage readAsRenderedImage(int imageIndex, + ImageReadParam param) + throws IOException { + return read(imageIndex, param); + } + + // Thumbnails + + /** + * Returns true if the image format understood by + * this reader supports thumbnail preview images associated with + * it. The default implementation returns false. + * + *

If this method returns false, + * hasThumbnails and getNumThumbnails + * will return false and 0, + * respectively, and readThumbnail will throw an + * UnsupportedOperationException, regardless of their + * arguments. + * + *

A reader that does not support thumbnails need not + * implement any of the thumbnail-related methods. + * + * @return true if thumbnails are supported. + */ + public boolean readerSupportsThumbnails() { + return false; + } + + /** + * Returns true if the given image has thumbnail + * preview images associated with it. If the format does not + * support thumbnails (readerSupportsThumbnails + * returns false), false will be + * returned regardless of whether an input source has been set or + * whether imageIndex is in bounds. + * + *

The default implementation returns true if + * getNumThumbnails returns a value greater than 0. + * + * @param imageIndex the index of the image being queried. + * + * @return true if the given image has thumbnails. + * + * @exception IllegalStateException if the reader supports + * thumbnails but the input source has not been set. + * @exception IndexOutOfBoundsException if the reader supports + * thumbnails but imageIndex is out of bounds. + * @exception IOException if an error occurs during reading. + */ + public boolean hasThumbnails(int imageIndex) throws IOException { + return getNumThumbnails(imageIndex) > 0; + } + + /** + * Returns the number of thumbnail preview images associated with + * the given image. If the format does not support thumbnails, + * (readerSupportsThumbnails returns + * false), 0 will be returned regardless + * of whether an input source has been set or whether + * imageIndex is in bounds. + * + *

The default implementation returns 0 without checking its + * argument. + * + * @param imageIndex the index of the image being queried. + * + * @return the number of thumbnails associated with the given + * image. + * + * @exception IllegalStateException if the reader supports + * thumbnails but the input source has not been set. + * @exception IndexOutOfBoundsException if the reader supports + * thumbnails but imageIndex is out of bounds. + * @exception IOException if an error occurs during reading. + */ + public int getNumThumbnails(int imageIndex) + throws IOException { + return 0; + } + + /** + * Returns the width of the thumbnail preview image indexed by + * thumbnailIndex, associated with the image indexed + * by ImageIndex. + * + *

If the reader does not support thumbnails, + * (readerSupportsThumbnails returns + * false), an UnsupportedOperationException + * will be thrown. + * + *

The default implementation simply returns + * readThumbnail(imageindex, + * thumbnailIndex).getWidth(). Subclasses should therefore + * override this method if possible in order to avoid forcing the + * thumbnail to be read. + * + * @param imageIndex the index of the image to be retrieved. + * @param thumbnailIndex the index of the thumbnail to be retrieved. + * + * @return the width of the desired thumbnail as an int. + * + * @exception UnsupportedOperationException if thumbnails are not + * supported. + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if either of the supplied + * indices are out of bounds. + * @exception IOException if an error occurs during reading. + */ + public int getThumbnailWidth(int imageIndex, int thumbnailIndex) + throws IOException { + return readThumbnail(imageIndex, thumbnailIndex).getWidth(); + } + + /** + * Returns the height of the thumbnail preview image indexed by + * thumbnailIndex, associated with the image indexed + * by ImageIndex. + * + *

If the reader does not support thumbnails, + * (readerSupportsThumbnails returns + * false), an UnsupportedOperationException + * will be thrown. + * + *

The default implementation simply returns + * readThumbnail(imageindex, + * thumbnailIndex).getHeight(). Subclasses should + * therefore override this method if possible in order to avoid + * forcing the thumbnail to be read. + * + * @param imageIndex the index of the image to be retrieved. + * @param thumbnailIndex the index of the thumbnail to be retrieved. + * + * @return the height of the desired thumbnail as an int. + * + * @exception UnsupportedOperationException if thumbnails are not + * supported. + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if either of the supplied + * indices are out of bounds. + * @exception IOException if an error occurs during reading. + */ + public int getThumbnailHeight(int imageIndex, int thumbnailIndex) + throws IOException { + return readThumbnail(imageIndex, thumbnailIndex).getHeight(); + } + + /** + * Returns the thumbnail preview image indexed by + * thumbnailIndex, associated with the image indexed + * by ImageIndex as a BufferedImage. + * + *

Any registered IIOReadProgressListener objects + * will be notified by calling their + * thumbnailStarted, thumbnailProgress, + * and thumbnailComplete methods. + * + *

If the reader does not support thumbnails, + * (readerSupportsThumbnails returns + * false), an UnsupportedOperationException + * will be thrown regardless of whether an input source has been + * set or whether the indices are in bounds. + * + *

The default implementation throws an + * UnsupportedOperationException. + * + * @param imageIndex the index of the image to be retrieved. + * @param thumbnailIndex the index of the thumbnail to be retrieved. + * + * @return the desired thumbnail as a BufferedImage. + * + * @exception UnsupportedOperationException if thumbnails are not + * supported. + * @exception IllegalStateException if the input source has not been set. + * @exception IndexOutOfBoundsException if either of the supplied + * indices are out of bounds. + * @exception IOException if an error occurs during reading. + */ + public BufferedImage readThumbnail(int imageIndex, + int thumbnailIndex) + throws IOException { + throw new UnsupportedOperationException("Thumbnails not supported!"); + } + + // Abort + + /** + * Requests that any current read operation be aborted. The + * contents of the image following the abort will be undefined. + * + *

Readers should call clearAbortRequest at the + * beginning of each read operation, and poll the value of + * abortRequested regularly during the read. + */ + public synchronized void abort() { + this.abortFlag = true; + } + + /** + * Returns true if a request to abort the current + * read operation has been made since the reader was instantiated or + * clearAbortRequest was called. + * + * @return true if the current read operation should + * be aborted. + * + * @see #abort + * @see #clearAbortRequest + */ + protected synchronized boolean abortRequested() { + return this.abortFlag; + } + + /** + * Clears any previous abort request. After this method has been + * called, abortRequested will return + * false. + * + * @see #abort + * @see #abortRequested + */ + protected synchronized void clearAbortRequest() { + this.abortFlag = false; + } + + // Listeners + + // Add an element to a list, creating a new list if the + // existing list is null, and return the list. + static List addToList(List l, Object elt) { + if (l == null) { + l = new ArrayList(); + } + l.add(elt); + return l; + } + + + // Remove an element from a list, discarding the list if the + // resulting list is empty, and return the list or null. + static List removeFromList(List l, Object elt) { + if (l == null) { + return l; + } + l.remove(elt); + if (l.size() == 0) { + l = null; + } + return l; + } + + /** + * Adds an IIOReadWarningListener to the list of + * registered warning listeners. If listener is + * null, no exception will be thrown and no action + * will be taken. Messages sent to the given listener will be + * localized, if possible, to match the current + * Locale. If no Locale has been set, + * warning messages may be localized as the reader sees fit. + * + * @param listener an IIOReadWarningListener to be registered. + * + * @see #removeIIOReadWarningListener + */ + public void addIIOReadWarningListener(IIOReadWarningListener listener) { + if (listener == null) { + return; + } + warningListeners = addToList(warningListeners, listener); + warningLocales = addToList(warningLocales, getLocale()); + } + + /** + * Removes an IIOReadWarningListener from the list of + * registered error listeners. If the listener was not previously + * registered, or if listener is null, + * no exception will be thrown and no action will be taken. + * + * @param listener an IIOReadWarningListener to be unregistered. + * + * @see #addIIOReadWarningListener + */ + public void removeIIOReadWarningListener(IIOReadWarningListener listener) { + if (listener == null || warningListeners == null) { + return; + } + int index = warningListeners.indexOf(listener); + if (index != -1) { + warningListeners.remove(index); + warningLocales.remove(index); + if (warningListeners.size() == 0) { + warningListeners = null; + warningLocales = null; + } + } + } + + /** + * Removes all currently registered + * IIOReadWarningListener objects. + * + *

The default implementation sets the + * warningListeners and warningLocales + * instance variables to null. + */ + public void removeAllIIOReadWarningListeners() { + warningListeners = null; + warningLocales = null; + } + + /** + * Adds an IIOReadProgressListener to the list of + * registered progress listeners. If listener is + * null, no exception will be thrown and no action + * will be taken. + * + * @param listener an IIOReadProgressListener to be registered. + * + * @see #removeIIOReadProgressListener + */ + public void addIIOReadProgressListener(IIOReadProgressListener listener) { + if (listener == null) { + return; + } + progressListeners = addToList(progressListeners, listener); + } + + /** + * Removes an IIOReadProgressListener from the list + * of registered progress listeners. If the listener was not + * previously registered, or if listener is + * null, no exception will be thrown and no action + * will be taken. + * + * @param listener an IIOReadProgressListener to be unregistered. + * + * @see #addIIOReadProgressListener + */ + public void + removeIIOReadProgressListener (IIOReadProgressListener listener) { + if (listener == null || progressListeners == null) { + return; + } + progressListeners = removeFromList(progressListeners, listener); + } + + /** + * Removes all currently registered + * IIOReadProgressListener objects. + * + *

The default implementation sets the + * progressListeners instance variable to + * null. + */ + public void removeAllIIOReadProgressListeners() { + progressListeners = null; + } + + /** + * Adds an IIOReadUpdateListener to the list of + * registered update listeners. If listener is + * null, no exception will be thrown and no action + * will be taken. The listener will receive notification of pixel + * updates as images and thumbnails are decoded, including the + * starts and ends of progressive passes. + * + *

If no update listeners are present, the reader may choose + * to perform fewer updates to the pixels of the destination + * images and/or thumbnails, which may result in more efficient + * decoding. + * + *

For example, in progressive JPEG decoding each pass + * contains updates to a set of coefficients, which would have to + * be transformed into pixel values and converted to an RGB color + * space for each pass if listeners are present. If no listeners + * are present, the coefficients may simply be accumulated and the + * final results transformed and color converted one time only. + * + *

The final results of decoding will be the same whether or + * not intermediate updates are performed. Thus if only the final + * image is desired it may be preferable not to register any + * IIOReadUpdateListeners. In general, progressive + * updating is most effective when fetching images over a network + * connection that is very slow compared to local CPU processing; + * over a fast connection, progressive updates may actually slow + * down the presentation of the image. + * + * @param listener an IIOReadUpdateListener to be registered. + * + * @see #removeIIOReadUpdateListener + */ + public void + addIIOReadUpdateListener(IIOReadUpdateListener listener) { + if (listener == null) { + return; + } + updateListeners = addToList(updateListeners, listener); + } + + /** + * Removes an IIOReadUpdateListener from the list of + * registered update listeners. If the listener was not + * previously registered, or if listener is + * null, no exception will be thrown and no action + * will be taken. + * + * @param listener an IIOReadUpdateListener to be unregistered. + * + * @see #addIIOReadUpdateListener + */ + public void removeIIOReadUpdateListener(IIOReadUpdateListener listener) { + if (listener == null || updateListeners == null) { + return; + } + updateListeners = removeFromList(updateListeners, listener); + } + + /** + * Removes all currently registered + * IIOReadUpdateListener objects. + * + *

The default implementation sets the + * updateListeners instance variable to + * null. + */ + public void removeAllIIOReadUpdateListeners() { + updateListeners = null; + } + + /** + * Broadcasts the start of an sequence of image reads to all + * registered IIOReadProgressListeners by calling + * their sequenceStarted method. Subclasses may use + * this method as a convenience. + * + * @param minIndex the lowest index being read. + */ + protected void processSequenceStarted(int minIndex) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.sequenceStarted(this, minIndex); + } + } + + /** + * Broadcasts the completion of an sequence of image reads to all + * registered IIOReadProgressListeners by calling + * their sequenceComplete method. Subclasses may use + * this method as a convenience. + */ + protected void processSequenceComplete() { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.sequenceComplete(this); + } + } + + /** + * Broadcasts the start of an image read to all registered + * IIOReadProgressListeners by calling their + * imageStarted method. Subclasses may use this + * method as a convenience. + * + * @param imageIndex the index of the image about to be read. + */ + protected void processImageStarted(int imageIndex) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.imageStarted(this, imageIndex); + } + } + + /** + * Broadcasts the current percentage of image completion to all + * registered IIOReadProgressListeners by calling + * their imageProgress method. Subclasses may use + * this method as a convenience. + * + * @param percentageDone the current percentage of completion, + * as a float. + */ + protected void processImageProgress(float percentageDone) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.imageProgress(this, percentageDone); + } + } + + /** + * Broadcasts the completion of an image read to all registered + * IIOReadProgressListeners by calling their + * imageComplete method. Subclasses may use this + * method as a convenience. + */ + protected void processImageComplete() { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.imageComplete(this); + } + } + + /** + * Broadcasts the start of a thumbnail read to all registered + * IIOReadProgressListeners by calling their + * thumbnailStarted method. Subclasses may use this + * method as a convenience. + * + * @param imageIndex the index of the image associated with the + * thumbnail. + * @param thumbnailIndex the index of the thumbnail. + */ + protected void processThumbnailStarted(int imageIndex, + int thumbnailIndex) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.thumbnailStarted(this, imageIndex, thumbnailIndex); + } + } + + /** + * Broadcasts the current percentage of thumbnail completion to + * all registered IIOReadProgressListeners by calling + * their thumbnailProgress method. Subclasses may + * use this method as a convenience. + * + * @param percentageDone the current percentage of completion, + * as a float. + */ + protected void processThumbnailProgress(float percentageDone) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.thumbnailProgress(this, percentageDone); + } + } + + /** + * Broadcasts the completion of a thumbnail read to all registered + * IIOReadProgressListeners by calling their + * thumbnailComplete method. Subclasses may use this + * method as a convenience. + */ + protected void processThumbnailComplete() { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.thumbnailComplete(this); + } + } + + /** + * Broadcasts that the read has been aborted to all registered + * IIOReadProgressListeners by calling their + * readAborted method. Subclasses may use this + * method as a convenience. + */ + protected void processReadAborted() { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadProgressListener listener = + (IIOReadProgressListener)progressListeners.get(i); + listener.readAborted(this); + } + } + + /** + * Broadcasts the beginning of a progressive pass to all + * registered IIOReadUpdateListeners by calling their + * passStarted method. Subclasses may use this + * method as a convenience. + * + * @param theImage the BufferedImage being updated. + * @param pass the index of the current pass, starting with 0. + * @param minPass the index of the first pass that will be decoded. + * @param maxPass the index of the last pass that will be decoded. + * @param minX the X coordinate of the upper-left pixel included + * in the pass. + * @param minY the X coordinate of the upper-left pixel included + * in the pass. + * @param periodX the horizontal separation between pixels. + * @param periodY the vertical separation between pixels. + * @param bands an array of ints indicating the + * set of affected bands of the destination. + */ + protected void processPassStarted(BufferedImage theImage, + int pass, + int minPass, int maxPass, + int minX, int minY, + int periodX, int periodY, + int[] bands) { + if (updateListeners == null) { + return; + } + int numListeners = updateListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadUpdateListener listener = + (IIOReadUpdateListener)updateListeners.get(i); + listener.passStarted(this, theImage, pass, + minPass, + maxPass, + minX, minY, + periodX, periodY, + bands); + } + } + + /** + * Broadcasts the update of a set of samples to all registered + * IIOReadUpdateListeners by calling their + * imageUpdate method. Subclasses may use this + * method as a convenience. + * + * @param theImage the BufferedImage being updated. + * @param minX the X coordinate of the upper-left pixel included + * in the pass. + * @param minY the X coordinate of the upper-left pixel included + * in the pass. + * @param width the total width of the area being updated, including + * pixels being skipped if periodX > 1. + * @param height the total height of the area being updated, + * including pixels being skipped if periodY > 1. + * @param periodX the horizontal separation between pixels. + * @param periodY the vertical separation between pixels. + * @param bands an array of ints indicating the + * set of affected bands of the destination. + */ + protected void processImageUpdate(BufferedImage theImage, + int minX, int minY, + int width, int height, + int periodX, int periodY, + int[] bands) { + if (updateListeners == null) { + return; + } + int numListeners = updateListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadUpdateListener listener = + (IIOReadUpdateListener)updateListeners.get(i); + listener.imageUpdate(this, + theImage, + minX, minY, + width, height, + periodX, periodY, + bands); + } + } + + /** + * Broadcasts the end of a progressive pass to all + * registered IIOReadUpdateListeners by calling their + * passComplete method. Subclasses may use this + * method as a convenience. + * + * @param theImage the BufferedImage being updated. + */ + protected void processPassComplete(BufferedImage theImage) { + if (updateListeners == null) { + return; + } + int numListeners = updateListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadUpdateListener listener = + (IIOReadUpdateListener)updateListeners.get(i); + listener.passComplete(this, theImage); + } + } + + /** + * Broadcasts the beginning of a thumbnail progressive pass to all + * registered IIOReadUpdateListeners by calling their + * thumbnailPassStarted method. Subclasses may use this + * method as a convenience. + * + * @param theThumbnail the BufferedImage thumbnail + * being updated. + * @param pass the index of the current pass, starting with 0. + * @param minPass the index of the first pass that will be decoded. + * @param maxPass the index of the last pass that will be decoded. + * @param minX the X coordinate of the upper-left pixel included + * in the pass. + * @param minY the X coordinate of the upper-left pixel included + * in the pass. + * @param periodX the horizontal separation between pixels. + * @param periodY the vertical separation between pixels. + * @param bands an array of ints indicating the + * set of affected bands of the destination. + */ + protected void processThumbnailPassStarted(BufferedImage theThumbnail, + int pass, + int minPass, int maxPass, + int minX, int minY, + int periodX, int periodY, + int[] bands) { + if (updateListeners == null) { + return; + } + int numListeners = updateListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadUpdateListener listener = + (IIOReadUpdateListener)updateListeners.get(i); + listener.thumbnailPassStarted(this, theThumbnail, pass, + minPass, + maxPass, + minX, minY, + periodX, periodY, + bands); + } + } + + /** + * Broadcasts the update of a set of samples in a thumbnail image + * to all registered IIOReadUpdateListeners by + * calling their thumbnailUpdate method. Subclasses may + * use this method as a convenience. + * + * @param theThumbnail the BufferedImage thumbnail + * being updated. + * @param minX the X coordinate of the upper-left pixel included + * in the pass. + * @param minY the X coordinate of the upper-left pixel included + * in the pass. + * @param width the total width of the area being updated, including + * pixels being skipped if periodX > 1. + * @param height the total height of the area being updated, + * including pixels being skipped if periodY > 1. + * @param periodX the horizontal separation between pixels. + * @param periodY the vertical separation between pixels. + * @param bands an array of ints indicating the + * set of affected bands of the destination. + */ + protected void processThumbnailUpdate(BufferedImage theThumbnail, + int minX, int minY, + int width, int height, + int periodX, int periodY, + int[] bands) { + if (updateListeners == null) { + return; + } + int numListeners = updateListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadUpdateListener listener = + (IIOReadUpdateListener)updateListeners.get(i); + listener.thumbnailUpdate(this, + theThumbnail, + minX, minY, + width, height, + periodX, periodY, + bands); + } + } + + /** + * Broadcasts the end of a thumbnail progressive pass to all + * registered IIOReadUpdateListeners by calling their + * thumbnailPassComplete method. Subclasses may use this + * method as a convenience. + * + * @param theThumbnail the BufferedImage thumbnail + * being updated. + */ + protected void processThumbnailPassComplete(BufferedImage theThumbnail) { + if (updateListeners == null) { + return; + } + int numListeners = updateListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadUpdateListener listener = + (IIOReadUpdateListener)updateListeners.get(i); + listener.thumbnailPassComplete(this, theThumbnail); + } + } + + /** + * Broadcasts a warning message to all registered + * IIOReadWarningListeners by calling their + * warningOccurred method. Subclasses may use this + * method as a convenience. + * + * @param warning the warning message to send. + * + * @exception IllegalArgumentException if warning + * is null. + */ + protected void processWarningOccurred(String warning) { + if (warningListeners == null) { + return; + } + if (warning == null) { + throw new IllegalArgumentException("warning == null!"); + } + int numListeners = warningListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadWarningListener listener = + (IIOReadWarningListener)warningListeners.get(i); + + listener.warningOccurred(this, warning); + } + } + + /** + * Broadcasts a localized warning message to all registered + * IIOReadWarningListeners by calling their + * warningOccurred method with a string taken + * from a ResourceBundle. Subclasses may use this + * method as a convenience. + * + * @param baseName the base name of a set of + * ResourceBundles containing localized warning + * messages. + * @param keyword the keyword used to index the warning message + * within the set of ResourceBundles. + * + * @exception IllegalArgumentException if baseName + * is null. + * @exception IllegalArgumentException if keyword + * is null. + * @exception IllegalArgumentException if no appropriate + * ResourceBundle may be located. + * @exception IllegalArgumentException if the named resource is + * not found in the located ResourceBundle. + * @exception IllegalArgumentException if the object retrieved + * from the ResourceBundle is not a + * String. + */ + protected void processWarningOccurred(String baseName, + String keyword) { + if (warningListeners == null) { + return; + } + if (baseName == null) { + throw new IllegalArgumentException("baseName == null!"); + } + if (keyword == null) { + throw new IllegalArgumentException("keyword == null!"); + } + int numListeners = warningListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOReadWarningListener listener = + (IIOReadWarningListener)warningListeners.get(i); + Locale locale = (Locale)warningLocales.get(i); + if (locale == null) { + locale = Locale.getDefault(); + } + + /** + * If an applet supplies an implementation of ImageReader and + * resource bundles, then the resource bundle will need to be + * accessed via the applet class loader. So first try the context + * class loader to locate the resource bundle. + * If that throws MissingResourceException, then try the + * system class loader. + */ + ClassLoader loader = (ClassLoader) + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + + ResourceBundle bundle = null; + try { + bundle = ResourceBundle.getBundle(baseName, locale, loader); + } catch (MissingResourceException mre) { + try { + bundle = ResourceBundle.getBundle(baseName, locale); + } catch (MissingResourceException mre1) { + throw new IllegalArgumentException("Bundle not found!"); + } + } + + String warning = null; + try { + warning = bundle.getString(keyword); + } catch (ClassCastException cce) { + throw new IllegalArgumentException("Resource is not a String!"); + } catch (MissingResourceException mre) { + throw new IllegalArgumentException("Resource is missing!"); + } + + listener.warningOccurred(this, warning); + } + } + + // State management + + /** + * Restores the ImageReader to its initial state. + * + *

The default implementation calls setInput(null, + * false), setLocale(null), + * removeAllIIOReadUpdateListeners(), + * removeAllIIOReadWarningListeners(), + * removeAllIIOReadProgressListeners(), and + * clearAbortRequest. + */ + public void reset() { + setInput(null, false, false); + setLocale(null); + removeAllIIOReadUpdateListeners(); + removeAllIIOReadProgressListeners(); + removeAllIIOReadWarningListeners(); + clearAbortRequest(); + } + + /** + * Allows any resources held by this object to be released. The + * result of calling any other method (other than + * finalize) subsequent to a call to this method + * is undefined. + * + *

It is important for applications to call this method when they + * know they will no longer be using this ImageReader. + * Otherwise, the reader may continue to hold on to resources + * indefinitely. + * + *

The default implementation of this method in the superclass does + * nothing. Subclass implementations should ensure that all resources, + * especially native resources, are released. + */ + public void dispose() { + } + + // Utility methods + + /** + * A utility method that may be used by readers to compute the + * region of the source image that should be read, taking into + * account any source region and subsampling offset settings in + * the supplied ImageReadParam. The actual + * subsampling factors, destination size, and destination offset + * are not taken into consideration, thus further + * clipping must take place. The {@link #computeRegions computeRegions} + * method performs all necessary clipping. + * + * @param param the ImageReadParam being used, or + * null. + * @param srcWidth the width of the source image. + * @param srcHeight the height of the source image. + * + * @return the source region as a Rectangle. + */ + protected static Rectangle getSourceRegion(ImageReadParam param, + int srcWidth, + int srcHeight) { + Rectangle sourceRegion = new Rectangle(0, 0, srcWidth, srcHeight); + if (param != null) { + Rectangle region = param.getSourceRegion(); + if (region != null) { + sourceRegion = sourceRegion.intersection(region); + } + + int subsampleXOffset = param.getSubsamplingXOffset(); + int subsampleYOffset = param.getSubsamplingYOffset(); + sourceRegion.x += subsampleXOffset; + sourceRegion.y += subsampleYOffset; + sourceRegion.width -= subsampleXOffset; + sourceRegion.height -= subsampleYOffset; + } + + return sourceRegion; + } + + /** + * Computes the source region of interest and the destination + * region of interest, taking the width and height of the source + * image, an optional destination image, and an optional + * ImageReadParam into account. The source region + * begins with the entire source image. Then that is clipped to + * the source region specified in the ImageReadParam, + * if one is specified. + * + *

If either of the destination offsets are negative, the + * source region is clipped so that its top left will coincide + * with the top left of the destination image, taking subsampling + * into account. Then the result is clipped to the destination + * image on the right and bottom, if one is specified, taking + * subsampling and destination offsets into account. + * + *

Similarly, the destination region begins with the source + * image, is translated to the destination offset given in the + * ImageReadParam if there is one, and finally is + * clipped to the destination image, if there is one. + * + *

If either the source or destination regions end up having a + * width or height of 0, an IllegalArgumentException + * is thrown. + * + *

The {@link #getSourceRegion getSourceRegion>} + * method may be used if only source clipping is desired. + * + * @param param an ImageReadParam, or null. + * @param srcWidth the width of the source image. + * @param srcHeight the height of the source image. + * @param image a BufferedImage that will be the + * destination image, or null. + * @param srcRegion a Rectangle that will be filled with + * the source region of interest. + * @param destRegion a Rectangle that will be filled with + * the destination region of interest. + * @exception IllegalArgumentException if srcRegion + * is null. + * @exception IllegalArgumentException if dstRegion + * is null. + * @exception IllegalArgumentException if the resulting source or + * destination region is empty. + */ + protected static void computeRegions(ImageReadParam param, + int srcWidth, + int srcHeight, + BufferedImage image, + Rectangle srcRegion, + Rectangle destRegion) { + if (srcRegion == null) { + throw new IllegalArgumentException("srcRegion == null!"); + } + if (destRegion == null) { + throw new IllegalArgumentException("destRegion == null!"); + } + + // Start with the entire source image + srcRegion.setBounds(0, 0, srcWidth, srcHeight); + + // Destination also starts with source image, as that is the + // maximum extent if there is no subsampling + destRegion.setBounds(0, 0, srcWidth, srcHeight); + + // Clip that to the param region, if there is one + int periodX = 1; + int periodY = 1; + int gridX = 0; + int gridY = 0; + if (param != null) { + Rectangle paramSrcRegion = param.getSourceRegion(); + if (paramSrcRegion != null) { + srcRegion.setBounds(srcRegion.intersection(paramSrcRegion)); + } + periodX = param.getSourceXSubsampling(); + periodY = param.getSourceYSubsampling(); + gridX = param.getSubsamplingXOffset(); + gridY = param.getSubsamplingYOffset(); + srcRegion.translate(gridX, gridY); + srcRegion.width -= gridX; + srcRegion.height -= gridY; + destRegion.setLocation(param.getDestinationOffset()); + } + + // Now clip any negative destination offsets, i.e. clip + // to the top and left of the destination image + if (destRegion.x < 0) { + int delta = -destRegion.x*periodX; + srcRegion.x += delta; + srcRegion.width -= delta; + destRegion.x = 0; + } + if (destRegion.y < 0) { + int delta = -destRegion.y*periodY; + srcRegion.y += delta; + srcRegion.height -= delta; + destRegion.y = 0; + } + + // Now clip the destination Region to the subsampled width and height + int subsampledWidth = (srcRegion.width + periodX - 1)/periodX; + int subsampledHeight = (srcRegion.height + periodY - 1)/periodY; + destRegion.width = subsampledWidth; + destRegion.height = subsampledHeight; + + // Now clip that to right and bottom of the destination image, + // if there is one, taking subsampling into account + if (image != null) { + Rectangle destImageRect = new Rectangle(0, 0, + image.getWidth(), + image.getHeight()); + destRegion.setBounds(destRegion.intersection(destImageRect)); + if (destRegion.isEmpty()) { + throw new IllegalArgumentException + ("Empty destination region!"); + } + + int deltaX = destRegion.x + subsampledWidth - image.getWidth(); + if (deltaX > 0) { + srcRegion.width -= deltaX*periodX; + } + int deltaY = destRegion.y + subsampledHeight - image.getHeight(); + if (deltaY > 0) { + srcRegion.height -= deltaY*periodY; + } + } + if (srcRegion.isEmpty() || destRegion.isEmpty()) { + throw new IllegalArgumentException("Empty region!"); + } + } + + /** + * A utility method that may be used by readers to test the + * validity of the source and destination band settings of an + * ImageReadParam. This method may be called as soon + * as the reader knows both the number of bands of the source + * image as it exists in the input stream, and the number of bands + * of the destination image that being written. + * + *

The method retrieves the source and destination band + * setting arrays from param using the getSourceBands + * and getDestinationBandsmethods (or considers them + * to be null if param is + * null). If the source band setting array is + * null, it is considered to be equal to the array + * { 0, 1, ..., numSrcBands - 1 }, and similarly for + * the destination band setting array. + * + *

The method then tests that both arrays are equal in length, + * and that neither array contains a value larger than the largest + * available band index. + * + *

Any failure results in an + * IllegalArgumentException being thrown; success + * results in the method returning silently. + * + * @param param the ImageReadParam being used to read + * the image. + * @param numSrcBands the number of bands of the image as it exists + * int the input source. + * @param numDstBands the number of bands in the destination image + * being written. + * + * @exception IllegalArgumentException if param + * contains an invalid specification of a source and/or + * destination band subset. + */ + protected static void checkReadParamBandSettings(ImageReadParam param, + int numSrcBands, + int numDstBands) { + // A null param is equivalent to srcBands == dstBands == null. + int[] srcBands = null; + int[] dstBands = null; + if (param != null) { + srcBands = param.getSourceBands(); + dstBands = param.getDestinationBands(); + } + + int paramSrcBandLength = + (srcBands == null) ? numSrcBands : srcBands.length; + int paramDstBandLength = + (dstBands == null) ? numDstBands : dstBands.length; + + if (paramSrcBandLength != paramDstBandLength) { + throw new IllegalArgumentException("ImageReadParam num source & dest bands differ!"); + } + + if (srcBands != null) { + for (int i = 0; i < srcBands.length; i++) { + if (srcBands[i] >= numSrcBands) { + throw new IllegalArgumentException("ImageReadParam source bands contains a value >= the number of source bands!"); + } + } + } + + if (dstBands != null) { + for (int i = 0; i < dstBands.length; i++) { + if (dstBands[i] >= numDstBands) { + throw new IllegalArgumentException("ImageReadParam dest bands contains a value >= the number of dest bands!"); + } + } + } + } + + /** + * Returns the BufferedImage to which decoded pixel + * data should be written. The image is determined by inspecting + * the supplied ImageReadParam if it is + * non-null; if its getDestination + * method returns a non-null value, that image is + * simply returned. Otherwise, + * param.getDestinationType method is called to + * determine if a particular image type has been specified. If + * so, the returned ImageTypeSpecifier is used after + * checking that it is equal to one of those included in + * imageTypes. + * + *

If param is null or the above + * steps have not yielded an image or an + * ImageTypeSpecifier, the first value obtained from + * the imageTypes parameter is used. Typically, the + * caller will set imageTypes to the value of + * getImageTypes(imageIndex). + * + *

Next, the dimensions of the image are determined by a call + * to computeRegions. The actual width and height of + * the image being decoded are passed in as the width + * and height parameters. + * + * @param param an ImageReadParam to be used to get + * the destination image or image type, or null. + * @param imageTypes an Iterator of + * ImageTypeSpecifiers indicating the legal image + * types, with the default first. + * @param width the true width of the image or tile begin decoded. + * @param height the true width of the image or tile being decoded. + * + * @return the BufferedImage to which decoded pixel + * data should be written. + * + * @exception IIOException if the ImageTypeSpecifier + * specified by param does not match any of the legal + * ones from imageTypes. + * @exception IllegalArgumentException if imageTypes + * is null or empty, or if an object not of type + * ImageTypeSpecifier is retrieved from it. + * @exception IllegalArgumentException if the resulting image would + * have a width or height less than 1. + * @exception IllegalArgumentException if the product of + * width and height is greater than + * Integer.MAX_VALUE. + */ + protected static BufferedImage + getDestination(ImageReadParam param, + Iterator imageTypes, + int width, int height) + throws IIOException { + if (imageTypes == null || !imageTypes.hasNext()) { + throw new IllegalArgumentException("imageTypes null or empty!"); + } + if ((long)width*height > Integer.MAX_VALUE) { + throw new IllegalArgumentException + ("width*height > Integer.MAX_VALUE!"); + } + + BufferedImage dest = null; + ImageTypeSpecifier imageType = null; + + // If param is non-null, use it + if (param != null) { + // Try to get the image itself + dest = param.getDestination(); + if (dest != null) { + return dest; + } + + // No image, get the image type + imageType = param.getDestinationType(); + } + + // No info from param, use fallback image type + if (imageType == null) { + Object o = imageTypes.next(); + if (!(o instanceof ImageTypeSpecifier)) { + throw new IllegalArgumentException + ("Non-ImageTypeSpecifier retrieved from imageTypes!"); + } + imageType = (ImageTypeSpecifier)o; + } else { + boolean foundIt = false; + while (imageTypes.hasNext()) { + ImageTypeSpecifier type = + (ImageTypeSpecifier)imageTypes.next(); + if (type.equals(imageType)) { + foundIt = true; + break; + } + } + + if (!foundIt) { + throw new IIOException + ("Destination type from ImageReadParam does not match!"); + } + } + + Rectangle srcRegion = new Rectangle(0,0,0,0); + Rectangle destRegion = new Rectangle(0,0,0,0); + computeRegions(param, + width, + height, + null, + srcRegion, + destRegion); + + int destWidth = destRegion.x + destRegion.width; + int destHeight = destRegion.y + destRegion.height; + // Create a new image based on the type specifier + return imageType.createBufferedImage(destWidth, destHeight); + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/ImageTranscoder.java b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageTranscoder.java new file mode 100644 index 000000000..e2bd68c72 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageTranscoder.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2000, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio; + +import javax.imageio.metadata.IIOMetadata; + +/** + * An interface providing metadata transcoding capability. + * + *

Any image may be transcoded (written to a different format + * than the one it was originally stored in) simply by performing + * a read operation followed by a write operation. However, loss + * of data may occur in this process due to format differences. + * + *

In general, the best results will be achieved when + * format-specific metadata objects can be created to encapsulate as + * much information about the image and its associated metadata as + * possible, in terms that are understood by the specific + * ImageWriter used to perform the encoding. + * + *

An ImageTranscoder may be used to convert the + * IIOMetadata objects supplied by the + * ImageReader (representing per-stream and per-image + * metadata) into corresponding objects suitable for encoding by a + * particular ImageWriter. In the case where the methods + * of this interface are being called directly on an + * ImageWriter, the output will be suitable for that + * writer. + * + *

The internal details of converting an IIOMetadata + * object into a writer-specific format will vary according to the + * context of the operation. Typically, an ImageWriter + * will inspect the incoming object to see if it implements additional + * interfaces with which the writer is familiar. This might be the + * case, for example, if the object was obtained by means of a read + * operation on a reader plug-in written by the same vendor as the + * writer. In this case, the writer may access the incoming object by + * means of its plug-in specific interfaces. In this case, the + * re-encoding may be close to lossless if the image file format is + * kept constant. If the format is changing, the writer may still + * attempt to preserve as much information as possible. + * + *

If the incoming object does not implement any additional + * interfaces known to the writer, the writer has no choice but to + * access it via the standard IIOMetadata interfaces such + * as the tree view provided by IIOMetadata.getAsTree. + * In this case, there is likely to be significant loss of + * information. + * + *

An independent ImageTranscoder essentially takes + * on the same role as the writer plug-in in the above examples. It + * must be familiar with the private interfaces used by both the + * reader and writer plug-ins, and manually instantiate an object that + * will be usable by the writer. The resulting metadata objects may + * be used by the writer directly. + * + *

No independent implementations of ImageTranscoder + * are provided as part of the standard API. Instead, the intention + * of this interface is to provide a way for implementations to be + * created and discovered by applications as the need arises. + * + */ +public interface ImageTranscoder { + + /** + * Returns an IIOMetadata object that may be used for + * encoding and optionally modified using its document interfaces + * or other interfaces specific to the writer plug-in that will be + * used for encoding. + * + *

An optional ImageWriteParam may be supplied + * for cases where it may affect the structure of the stream + * metadata. + * + *

If the supplied ImageWriteParam contains + * optional setting values not understood by this writer or + * transcoder, they will be ignored. + * + * @param inData an IIOMetadata object representing + * stream metadata, used to initialize the state of the returned + * object. + * @param param an ImageWriteParam that will be used to + * encode the image, or null. + * + * @return an IIOMetadata object, or + * null if the plug-in does not provide metadata + * encoding capabilities. + * + * @exception IllegalArgumentException if inData is + * null. + */ + IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param); + + /** + * Returns an IIOMetadata object that may be used for + * encoding and optionally modified using its document interfaces + * or other interfaces specific to the writer plug-in that will be + * used for encoding. + * + *

An optional ImageWriteParam may be supplied + * for cases where it may affect the structure of the image + * metadata. + * + *

If the supplied ImageWriteParam contains + * optional setting values not understood by this writer or + * transcoder, they will be ignored. + * + * @param inData an IIOMetadata object representing + * image metadata, used to initialize the state of the returned + * object. + * @param imageType an ImageTypeSpecifier indicating + * the layout and color information of the image with which the + * metadata will be associated. + * @param param an ImageWriteParam that will be used to + * encode the image, or null. + * + * @return an IIOMetadata object, + * or null if the plug-in does not provide + * metadata encoding capabilities. + * + * @exception IllegalArgumentException if either of + * inData or imageType is + * null. + */ + IIOMetadata convertImageMetadata(IIOMetadata inData, + ImageTypeSpecifier imageType, + ImageWriteParam param); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/ImageTypeSpecifier.java b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageTypeSpecifier.java new file mode 100644 index 000000000..15b3eccac --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageTypeSpecifier.java @@ -0,0 +1,1241 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio; + +import java.awt.Point; +import java.awt.Transparency; +import java.awt.image.BandedSampleModel; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.color.ColorSpace; +import java.awt.image.IndexColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DirectColorModel; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.util.Hashtable; + +/** + * A class that allows the format of an image (in particular, its + * SampleModel and ColorModel) to be + * specified in a convenient manner. + * + */ +public class ImageTypeSpecifier { + + /** + * The ColorModel to be used as a prototype. + */ + protected ColorModel colorModel; + + /** + * A SampleModel to be used as a prototype. + */ + protected SampleModel sampleModel; + + /** + * Cached specifiers for all of the standard + * BufferedImage types. + */ + private static ImageTypeSpecifier[] BISpecifier; + private static ColorSpace sRGB; + // Initialize the standard specifiers + static { + sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB); + + BISpecifier = + new ImageTypeSpecifier[BufferedImage.TYPE_BYTE_INDEXED + 1]; + } + + /** + * A constructor to be used by inner subclasses only. + */ + private ImageTypeSpecifier() {} + + /** + * Constructs an ImageTypeSpecifier directly + * from a ColorModel and a SampleModel. + * It is the caller's responsibility to supply compatible + * parameters. + * + * @param colorModel a ColorModel. + * @param sampleModel a SampleModel. + * + * @exception IllegalArgumentException if either parameter is + * null. + * @exception IllegalArgumentException if sampleModel + * is not compatible with colorModel. + */ + public ImageTypeSpecifier(ColorModel colorModel, SampleModel sampleModel) { + if (colorModel == null) { + throw new IllegalArgumentException("colorModel == null!"); + } + if (sampleModel == null) { + throw new IllegalArgumentException("sampleModel == null!"); + } + if (!colorModel.isCompatibleSampleModel(sampleModel)) { + throw new IllegalArgumentException + ("sampleModel is incompatible with colorModel!"); + } + this.colorModel = colorModel; + this.sampleModel = sampleModel; + } + + /** + * Constructs an ImageTypeSpecifier from a + * RenderedImage. If a BufferedImage is + * being used, one of the factory methods + * createFromRenderedImage or + * createFromBufferedImageType should be used instead in + * order to get a more accurate result. + * + * @param image a RenderedImage. + * + * @exception IllegalArgumentException if the argument is + * null. + */ + public ImageTypeSpecifier(RenderedImage image) { + if (image == null) { + throw new IllegalArgumentException("image == null!"); + } + colorModel = image.getColorModel(); + sampleModel = image.getSampleModel(); + } + + // Packed + + static class Packed extends ImageTypeSpecifier { + ColorSpace colorSpace; + int redMask; + int greenMask; + int blueMask; + int alphaMask; + int transferType; + boolean isAlphaPremultiplied; + + public Packed(ColorSpace colorSpace, + int redMask, + int greenMask, + int blueMask, + int alphaMask, // 0 if no alpha + int transferType, + boolean isAlphaPremultiplied) { + if (colorSpace == null) { + throw new IllegalArgumentException("colorSpace == null!"); + } + if (colorSpace.getType() != ColorSpace.TYPE_RGB) { + throw new IllegalArgumentException + ("colorSpace is not of type TYPE_RGB!"); + } + if (transferType != DataBuffer.TYPE_BYTE && + transferType != DataBuffer.TYPE_USHORT && + transferType != DataBuffer.TYPE_INT) { + throw new IllegalArgumentException + ("Bad value for transferType!"); + } + if (redMask == 0 && greenMask == 0 && + blueMask == 0 && alphaMask == 0) { + throw new IllegalArgumentException + ("No mask has at least 1 bit set!"); + } + this.colorSpace = colorSpace; + this.redMask = redMask; + this.greenMask = greenMask; + this.blueMask = blueMask; + this.alphaMask = alphaMask; + this.transferType = transferType; + this.isAlphaPremultiplied = isAlphaPremultiplied; + + int bits = 32; + this.colorModel = + new DirectColorModel(colorSpace, + bits, + redMask, greenMask, blueMask, + alphaMask, isAlphaPremultiplied, + transferType); + this.sampleModel = colorModel.createCompatibleSampleModel(1, 1); + } + } + + /** + * Returns a specifier for a packed image format that will use a + * DirectColorModel and a packed + * SampleModel to store each pixel packed into in a + * single byte, short, or int. + * + * @param colorSpace the desired ColorSpace. + * @param redMask a contiguous mask indicated the position of the + * red channel. + * @param greenMask a contiguous mask indicated the position of the + * green channel. + * @param blueMask a contiguous mask indicated the position of the + * blue channel. + * @param alphaMask a contiguous mask indicated the position of the + * alpha channel. + * @param transferType the desired SampleModel transfer type. + * @param isAlphaPremultiplied true if the color channels + * will be premultipled by the alpha channel. + * + * @return an ImageTypeSpecifier with the desired + * characteristics. + * + * @exception IllegalArgumentException if colorSpace + * is null. + * @exception IllegalArgumentException if colorSpace + * is not of type TYPE_RGB. + * @exception IllegalArgumentException if no mask has at least 1 + * bit set. + * @exception IllegalArgumentException if + * transferType if not one of + * DataBuffer.TYPE_BYTE, + * DataBuffer.TYPE_USHORT, or + * DataBuffer.TYPE_INT. + */ + public static ImageTypeSpecifier + createPacked(ColorSpace colorSpace, + int redMask, + int greenMask, + int blueMask, + int alphaMask, // 0 if no alpha + int transferType, + boolean isAlphaPremultiplied) { + return new ImageTypeSpecifier.Packed(colorSpace, + redMask, + greenMask, + blueMask, + alphaMask, // 0 if no alpha + transferType, + isAlphaPremultiplied); + } + + static ColorModel createComponentCM(ColorSpace colorSpace, + int numBands, + int dataType, + boolean hasAlpha, + boolean isAlphaPremultiplied) { + int transparency = + hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE; + + int[] numBits = new int[numBands]; + int bits = DataBuffer.getDataTypeSize(dataType); + + for (int i = 0; i < numBands; i++) { + numBits[i] = bits; + } + + return new ComponentColorModel(colorSpace, + numBits, + hasAlpha, + isAlphaPremultiplied, + transparency, + dataType); + } + + // Interleaved + + static class Interleaved extends ImageTypeSpecifier { + ColorSpace colorSpace; + int[] bandOffsets; + int dataType; + boolean hasAlpha; + boolean isAlphaPremultiplied; + + public Interleaved(ColorSpace colorSpace, + int[] bandOffsets, + int dataType, + boolean hasAlpha, + boolean isAlphaPremultiplied) { + if (colorSpace == null) { + throw new IllegalArgumentException("colorSpace == null!"); + } + if (bandOffsets == null) { + throw new IllegalArgumentException("bandOffsets == null!"); + } + int numBands = colorSpace.getNumComponents() + + (hasAlpha ? 1 : 0); + if (bandOffsets.length != numBands) { + throw new IllegalArgumentException + ("bandOffsets.length is wrong!"); + } + if (dataType != DataBuffer.TYPE_BYTE && + dataType != DataBuffer.TYPE_SHORT && + dataType != DataBuffer.TYPE_USHORT && + dataType != DataBuffer.TYPE_INT && + dataType != DataBuffer.TYPE_FLOAT && + dataType != DataBuffer.TYPE_DOUBLE) { + throw new IllegalArgumentException + ("Bad value for dataType!"); + } + this.colorSpace = colorSpace; + this.bandOffsets = (int[])bandOffsets.clone(); + this.dataType = dataType; + this.hasAlpha = hasAlpha; + this.isAlphaPremultiplied = isAlphaPremultiplied; + + this.colorModel = + ImageTypeSpecifier.createComponentCM(colorSpace, + bandOffsets.length, + dataType, + hasAlpha, + isAlphaPremultiplied); + + int minBandOffset = bandOffsets[0]; + int maxBandOffset = minBandOffset; + for (int i = 0; i < bandOffsets.length; i++) { + int offset = bandOffsets[i]; + minBandOffset = Math.min(offset, minBandOffset); + maxBandOffset = Math.max(offset, maxBandOffset); + } + int pixelStride = maxBandOffset - minBandOffset + 1; + + int w = 1; + int h = 1; + this.sampleModel = + new PixelInterleavedSampleModel(dataType, + w, h, + pixelStride, + w*pixelStride, + bandOffsets); + } + + public boolean equals(Object o) { + if ((o == null) || + !(o instanceof ImageTypeSpecifier.Interleaved)) { + return false; + } + + ImageTypeSpecifier.Interleaved that = + (ImageTypeSpecifier.Interleaved)o; + + if ((!(this.colorSpace.equals(that.colorSpace))) || + (this.dataType != that.dataType) || + (this.hasAlpha != that.hasAlpha) || + (this.isAlphaPremultiplied != that.isAlphaPremultiplied) || + (this.bandOffsets.length != that.bandOffsets.length)) { + return false; + } + + for (int i = 0; i < bandOffsets.length; i++) { + if (this.bandOffsets[i] != that.bandOffsets[i]) { + return false; + } + } + + return true; + } + + public int hashCode() { + return (super.hashCode() + + (4 * bandOffsets.length) + + (25 * dataType) + + (hasAlpha ? 17 : 18)); + } + } + + /** + * Returns a specifier for an interleaved image format that will + * use a ComponentColorModel and a + * PixelInterleavedSampleModel to store each pixel + * component in a separate byte, short, or int. + * + * @param colorSpace the desired ColorSpace. + * @param bandOffsets an array of ints indicating the + * offsets for each band. + * @param dataType the desired data type, as one of the enumerations + * from the DataBuffer class. + * @param hasAlpha true if an alpha channel is desired. + * @param isAlphaPremultiplied true if the color channels + * will be premultipled by the alpha channel. + * + * @return an ImageTypeSpecifier with the desired + * characteristics. + * + * @exception IllegalArgumentException if colorSpace + * is null. + * @exception IllegalArgumentException if bandOffsets + * is null. + * @exception IllegalArgumentException if dataType is + * not one of the legal DataBuffer.TYPE_* constants. + * @exception IllegalArgumentException if + * bandOffsets.length does not equal the number of + * color space components, plus 1 if hasAlpha is + * true. + */ + public static ImageTypeSpecifier + createInterleaved(ColorSpace colorSpace, + int[] bandOffsets, + int dataType, + boolean hasAlpha, + boolean isAlphaPremultiplied) { + return new ImageTypeSpecifier.Interleaved(colorSpace, + bandOffsets, + dataType, + hasAlpha, + isAlphaPremultiplied); + } + + // Banded + + static class Banded extends ImageTypeSpecifier { + ColorSpace colorSpace; + int[] bankIndices; + int[] bandOffsets; + int dataType; + boolean hasAlpha; + boolean isAlphaPremultiplied; + + public Banded(ColorSpace colorSpace, + int[] bankIndices, + int[] bandOffsets, + int dataType, + boolean hasAlpha, + boolean isAlphaPremultiplied) { + if (colorSpace == null) { + throw new IllegalArgumentException("colorSpace == null!"); + } + if (bankIndices == null) { + throw new IllegalArgumentException("bankIndices == null!"); + } + if (bandOffsets == null) { + throw new IllegalArgumentException("bandOffsets == null!"); + } + if (bankIndices.length != bandOffsets.length) { + throw new IllegalArgumentException + ("bankIndices.length != bandOffsets.length!"); + } + if (dataType != DataBuffer.TYPE_BYTE && + dataType != DataBuffer.TYPE_SHORT && + dataType != DataBuffer.TYPE_USHORT && + dataType != DataBuffer.TYPE_INT && + dataType != DataBuffer.TYPE_FLOAT && + dataType != DataBuffer.TYPE_DOUBLE) { + throw new IllegalArgumentException + ("Bad value for dataType!"); + } + int numBands = colorSpace.getNumComponents() + + (hasAlpha ? 1 : 0); + if (bandOffsets.length != numBands) { + throw new IllegalArgumentException + ("bandOffsets.length is wrong!"); + } + + this.colorSpace = colorSpace; + this.bankIndices = (int[])bankIndices.clone(); + this.bandOffsets = (int[])bandOffsets.clone(); + this.dataType = dataType; + this.hasAlpha = hasAlpha; + this.isAlphaPremultiplied = isAlphaPremultiplied; + + this.colorModel = + ImageTypeSpecifier.createComponentCM(colorSpace, + bankIndices.length, + dataType, + hasAlpha, + isAlphaPremultiplied); + + int w = 1; + int h = 1; + this.sampleModel = new BandedSampleModel(dataType, + w, h, + w, + bankIndices, + bandOffsets); + } + + public boolean equals(Object o) { + if ((o == null) || + !(o instanceof ImageTypeSpecifier.Banded)) { + return false; + } + + ImageTypeSpecifier.Banded that = + (ImageTypeSpecifier.Banded)o; + + if ((!(this.colorSpace.equals(that.colorSpace))) || + (this.dataType != that.dataType) || + (this.hasAlpha != that.hasAlpha) || + (this.isAlphaPremultiplied != that.isAlphaPremultiplied) || + (this.bankIndices.length != that.bankIndices.length) || + (this.bandOffsets.length != that.bandOffsets.length)) { + return false; + } + + for (int i = 0; i < bankIndices.length; i++) { + if (this.bankIndices[i] != that.bankIndices[i]) { + return false; + } + } + + for (int i = 0; i < bandOffsets.length; i++) { + if (this.bandOffsets[i] != that.bandOffsets[i]) { + return false; + } + } + + return true; + } + + public int hashCode() { + return (super.hashCode() + + (3 * bandOffsets.length) + + (7 * bankIndices.length) + + (21 * dataType) + + (hasAlpha ? 19 : 29)); + } + } + + /** + * Returns a specifier for a banded image format that will use a + * ComponentColorModel and a + * BandedSampleModel to store each channel in a + * separate array. + * + * @param colorSpace the desired ColorSpace. + * @param bankIndices an array of ints indicating the + * bank in which each band will be stored. + * @param bandOffsets an array of ints indicating the + * starting offset of each band within its bank. + * @param dataType the desired data type, as one of the enumerations + * from the DataBuffer class. + * @param hasAlpha true if an alpha channel is desired. + * @param isAlphaPremultiplied true if the color channels + * will be premultipled by the alpha channel. + * + * @return an ImageTypeSpecifier with the desired + * characteristics. + * + * @exception IllegalArgumentException if colorSpace + * is null. + * @exception IllegalArgumentException if bankIndices + * is null. + * @exception IllegalArgumentException if bandOffsets + * is null. + * @exception IllegalArgumentException if the lengths of + * bankIndices and bandOffsets differ. + * @exception IllegalArgumentException if + * bandOffsets.length does not equal the number of + * color space components, plus 1 if hasAlpha is + * true. + * @exception IllegalArgumentException if dataType is + * not one of the legal DataBuffer.TYPE_* constants. + */ + public static ImageTypeSpecifier + createBanded(ColorSpace colorSpace, + int[] bankIndices, + int[] bandOffsets, + int dataType, + boolean hasAlpha, + boolean isAlphaPremultiplied) { + return new ImageTypeSpecifier.Banded(colorSpace, + bankIndices, + bandOffsets, + dataType, + hasAlpha, + isAlphaPremultiplied); + } + + // Grayscale + + static class Grayscale extends ImageTypeSpecifier { + int bits; + int dataType; + boolean isSigned; + boolean hasAlpha; + boolean isAlphaPremultiplied; + + public Grayscale(int bits, + int dataType, + boolean isSigned, + boolean hasAlpha, + boolean isAlphaPremultiplied) + { + if (bits != 1 && bits != 2 && bits != 4 && + bits != 8 && bits != 16) + { + throw new IllegalArgumentException("Bad value for bits!"); + } + if (dataType != DataBuffer.TYPE_BYTE && + dataType != DataBuffer.TYPE_SHORT && + dataType != DataBuffer.TYPE_USHORT) + { + throw new IllegalArgumentException + ("Bad value for dataType!"); + } + if (bits > 8 && dataType == DataBuffer.TYPE_BYTE) { + throw new IllegalArgumentException + ("Too many bits for dataType!"); + } + + this.bits = bits; + this.dataType = dataType; + this.isSigned = isSigned; + this.hasAlpha = hasAlpha; + this.isAlphaPremultiplied = isAlphaPremultiplied; + + ColorSpace colorSpace = ColorSpace.getInstance(ColorSpace.CS_GRAY); + + if ((bits == 8 && dataType == DataBuffer.TYPE_BYTE) || + (bits == 16 && + (dataType == DataBuffer.TYPE_SHORT || + dataType == DataBuffer.TYPE_USHORT))) { + // Use component color model & sample model + + int numBands = hasAlpha ? 2 : 1; + int transparency = + hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE; + + + int[] nBits = new int[numBands]; + nBits[0] = bits; + if (numBands == 2) { + nBits[1] = bits; + } + this.colorModel = + new ComponentColorModel(colorSpace, + nBits, + hasAlpha, + isAlphaPremultiplied, + transparency, + dataType); + + int[] bandOffsets = new int[numBands]; + bandOffsets[0] = 0; + if (numBands == 2) { + bandOffsets[1] = 1; + } + + int w = 1; + int h = 1; + this.sampleModel = + new PixelInterleavedSampleModel(dataType, + w, h, + numBands, w*numBands, + bandOffsets); + } else { + int numEntries = 1 << bits; + byte[] arr = new byte[numEntries]; + for (int i = 0; i < numEntries; i++) { + arr[i] = (byte)(i*255/(numEntries - 1)); + } + this.colorModel = + new IndexColorModel(bits, numEntries, arr, arr, arr); + + this.sampleModel = + new MultiPixelPackedSampleModel(dataType, 1, 1, bits); + } + } + } + + /** + * Returns a specifier for a grayscale image format that will pack + * pixels of the given bit depth into array elements of + * the specified data type. + * + * @param bits the number of bits per gray value (1, 2, 4, 8, or 16). + * @param dataType the desired data type, as one of the enumerations + * from the DataBuffer class. + * @param isSigned true if negative values are to + * be represented. + * + * @return an ImageTypeSpecifier with the desired + * characteristics. + * + * @exception IllegalArgumentException if bits is + * not one of 1, 2, 4, 8, or 16. + * @exception IllegalArgumentException if dataType is + * not one of DataBuffer.TYPE_BYTE, + * DataBuffer.TYPE_SHORT, or + * DataBuffer.TYPE_USHORT. + * @exception IllegalArgumentException if bits is + * larger than the bit size of the given dataType. + */ + public static ImageTypeSpecifier + createGrayscale(int bits, + int dataType, + boolean isSigned) { + return new ImageTypeSpecifier.Grayscale(bits, + dataType, + isSigned, + false, + false); + } + + /** + * Returns a specifier for a grayscale plus alpha image format + * that will pack pixels of the given bit depth into array + * elements of the specified data type. + * + * @param bits the number of bits per gray value (1, 2, 4, 8, or 16). + * @param dataType the desired data type, as one of the enumerations + * from the DataBuffer class. + * @param isSigned true if negative values are to + * be represented. + * @param isAlphaPremultiplied true if the luminance channel + * will be premultipled by the alpha channel. + * + * @return an ImageTypeSpecifier with the desired + * characteristics. + * + * @exception IllegalArgumentException if bits is + * not one of 1, 2, 4, 8, or 16. + * @exception IllegalArgumentException if dataType is + * not one of DataBuffer.TYPE_BYTE, + * DataBuffer.TYPE_SHORT, or + * DataBuffer.TYPE_USHORT. + * @exception IllegalArgumentException if bits is + * larger than the bit size of the given dataType. + */ + public static ImageTypeSpecifier + createGrayscale(int bits, + int dataType, + boolean isSigned, + boolean isAlphaPremultiplied) { + return new ImageTypeSpecifier.Grayscale(bits, + dataType, + isSigned, + true, + isAlphaPremultiplied); + } + + // Indexed + + static class Indexed extends ImageTypeSpecifier { + byte[] redLUT; + byte[] greenLUT; + byte[] blueLUT; + byte[] alphaLUT = null; + int bits; + int dataType; + + public Indexed(byte[] redLUT, + byte[] greenLUT, + byte[] blueLUT, + byte[] alphaLUT, + int bits, + int dataType) { + if (redLUT == null || greenLUT == null || blueLUT == null) { + throw new IllegalArgumentException("LUT is null!"); + } + if (bits != 1 && bits != 2 && bits != 4 && + bits != 8 && bits != 16) { + throw new IllegalArgumentException("Bad value for bits!"); + } + if (dataType != DataBuffer.TYPE_BYTE && + dataType != DataBuffer.TYPE_SHORT && + dataType != DataBuffer.TYPE_USHORT && + dataType != DataBuffer.TYPE_INT) { + throw new IllegalArgumentException + ("Bad value for dataType!"); + } + if ((bits > 8 && dataType == DataBuffer.TYPE_BYTE) || + (bits > 16 && dataType != DataBuffer.TYPE_INT)) { + throw new IllegalArgumentException + ("Too many bits for dataType!"); + } + + int len = 1 << bits; + if (redLUT.length != len || + greenLUT.length != len || + blueLUT.length != len || + (alphaLUT != null && alphaLUT.length != len)) { + throw new IllegalArgumentException("LUT has improper length!"); + } + this.redLUT = (byte[])redLUT.clone(); + this.greenLUT = (byte[])greenLUT.clone(); + this.blueLUT = (byte[])blueLUT.clone(); + if (alphaLUT != null) { + this.alphaLUT = (byte[])alphaLUT.clone(); + } + this.bits = bits; + this.dataType = dataType; + + if (alphaLUT == null) { + this.colorModel = new IndexColorModel(bits, + redLUT.length, + redLUT, + greenLUT, + blueLUT); + } else { + this.colorModel = new IndexColorModel(bits, + redLUT.length, + redLUT, + greenLUT, + blueLUT, + alphaLUT); + } + + if ((bits == 8 && dataType == DataBuffer.TYPE_BYTE) || + (bits == 16 && + (dataType == DataBuffer.TYPE_SHORT || + dataType == DataBuffer.TYPE_USHORT))) { + int[] bandOffsets = { 0 }; + this.sampleModel = + new PixelInterleavedSampleModel(dataType, + 1, 1, 1, 1, + bandOffsets); + } else { + this.sampleModel = + new MultiPixelPackedSampleModel(dataType, 1, 1, bits); + } + } + } + + /** + * Returns a specifier for an indexed-color image format that will pack + * index values of the given bit depth into array elements of + * the specified data type. + * + * @param redLUT an array of bytes containing + * the red values for each index. + * @param greenLUT an array of bytes containing * the + * green values for each index. + * @param blueLUT an array of bytes containing the + * blue values for each index. + * @param alphaLUT an array of bytes containing the + * alpha values for each index, or null to create a + * fully opaque LUT. + * @param bits the number of bits in each index. + * @param dataType the desired output type, as one of the enumerations + * from the DataBuffer class. + * + * @return an ImageTypeSpecifier with the desired + * characteristics. + * + * @exception IllegalArgumentException if redLUT is + * null. + * @exception IllegalArgumentException if greenLUT is + * null. + * @exception IllegalArgumentException if blueLUT is + * null. + * @exception IllegalArgumentException if bits is + * not one of 1, 2, 4, 8, or 16. + * @exception IllegalArgumentException if the + * non-null LUT parameters do not have lengths of + * exactly {@code 1 << bits}. + * @exception IllegalArgumentException if dataType is + * not one of DataBuffer.TYPE_BYTE, + * DataBuffer.TYPE_SHORT, + * DataBuffer.TYPE_USHORT, + * or DataBuffer.TYPE_INT. + * @exception IllegalArgumentException if bits is + * larger than the bit size of the given dataType. + */ + public static ImageTypeSpecifier + createIndexed(byte[] redLUT, + byte[] greenLUT, + byte[] blueLUT, + byte[] alphaLUT, + int bits, + int dataType) { + return new ImageTypeSpecifier.Indexed(redLUT, + greenLUT, + blueLUT, + alphaLUT, + bits, + dataType); + } + + /** + * Returns an ImageTypeSpecifier that encodes + * one of the standard BufferedImage types + * (other than TYPE_CUSTOM). + * + * @param bufferedImageType an int representing one of the standard + * BufferedImage types. + * + * @return an ImageTypeSpecifier with the desired + * characteristics. + * + * @exception IllegalArgumentException if + * bufferedImageType is not one of the standard + * types, or is equal to TYPE_CUSTOM. + * + * @see java.awt.image.BufferedImage + * @see java.awt.image.BufferedImage#TYPE_INT_RGB + * @see java.awt.image.BufferedImage#TYPE_INT_ARGB + * @see java.awt.image.BufferedImage#TYPE_INT_ARGB_PRE + * @see java.awt.image.BufferedImage#TYPE_INT_BGR + * @see java.awt.image.BufferedImage#TYPE_3BYTE_BGR + * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR + * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR_PRE + * @see java.awt.image.BufferedImage#TYPE_USHORT_565_RGB + * @see java.awt.image.BufferedImage#TYPE_USHORT_555_RGB + * @see java.awt.image.BufferedImage#TYPE_BYTE_GRAY + * @see java.awt.image.BufferedImage#TYPE_USHORT_GRAY + * @see java.awt.image.BufferedImage#TYPE_BYTE_BINARY + * @see java.awt.image.BufferedImage#TYPE_BYTE_INDEXED + */ + public static + ImageTypeSpecifier createFromBufferedImageType(int bufferedImageType) { + if (bufferedImageType >= BufferedImage.TYPE_INT_RGB && + bufferedImageType <= BufferedImage.TYPE_BYTE_INDEXED) { + return getSpecifier(bufferedImageType); + } else if (bufferedImageType == BufferedImage.TYPE_CUSTOM) { + throw new IllegalArgumentException("Cannot create from TYPE_CUSTOM!"); + } else { + throw new IllegalArgumentException("Invalid BufferedImage type!"); + } + } + + /** + * Returns an ImageTypeSpecifier that encodes the + * layout of a RenderedImage (which may be a + * BufferedImage). + * + * @param image a RenderedImage. + * + * @return an ImageTypeSpecifier with the desired + * characteristics. + * + * @exception IllegalArgumentException if image is + * null. + */ + public static + ImageTypeSpecifier createFromRenderedImage(RenderedImage image) { + if (image == null) { + throw new IllegalArgumentException("image == null!"); + } + + if (image instanceof BufferedImage) { + int bufferedImageType = ((BufferedImage)image).getType(); + if (bufferedImageType != BufferedImage.TYPE_CUSTOM) { + return getSpecifier(bufferedImageType); + } + } + + return new ImageTypeSpecifier(image); + } + + /** + * Returns an int containing one of the enumerated constant values + * describing image formats from BufferedImage. + * + * @return an int representing a + * BufferedImage type. + * + * @see java.awt.image.BufferedImage + * @see java.awt.image.BufferedImage#TYPE_CUSTOM + * @see java.awt.image.BufferedImage#TYPE_INT_RGB + * @see java.awt.image.BufferedImage#TYPE_INT_ARGB + * @see java.awt.image.BufferedImage#TYPE_INT_ARGB_PRE + * @see java.awt.image.BufferedImage#TYPE_INT_BGR + * @see java.awt.image.BufferedImage#TYPE_3BYTE_BGR + * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR + * @see java.awt.image.BufferedImage#TYPE_4BYTE_ABGR_PRE + * @see java.awt.image.BufferedImage#TYPE_USHORT_565_RGB + * @see java.awt.image.BufferedImage#TYPE_USHORT_555_RGB + * @see java.awt.image.BufferedImage#TYPE_BYTE_GRAY + * @see java.awt.image.BufferedImage#TYPE_USHORT_GRAY + * @see java.awt.image.BufferedImage#TYPE_BYTE_BINARY + * @see java.awt.image.BufferedImage#TYPE_BYTE_INDEXED + */ + public int getBufferedImageType() { + BufferedImage bi = createBufferedImage(1, 1); + return bi.getType(); + } + + /** + * Return the number of color components + * specified by this object. This is the same value as returned by + * ColorModel.getNumComponents + * + * @return the number of components in the image. + */ + public int getNumComponents() { + return colorModel.getNumComponents(); + } + + /** + * Return the number of bands + * specified by this object. This is the same value as returned by + * SampleModel.getNumBands + * + * @return the number of bands in the image. + */ + public int getNumBands() { + return sampleModel.getNumBands(); + } + + /** + * Return the number of bits used to represent samples of the given band. + * + * @param band the index of the band to be queried, as an + * int. + * + * @return an int specifying a number of bits. + * + * @exception IllegalArgumentException if band is + * negative or greater than the largest band index. + */ + public int getBitsPerBand(int band) { + if (band < 0 | band >= getNumBands()) { + throw new IllegalArgumentException("band out of range!"); + } + return sampleModel.getSampleSize(band); + } + + /** + * Returns a SampleModel based on the settings + * encapsulated within this object. The width and height of the + * SampleModel will be set to arbitrary values. + * + * @return a SampleModel with arbitrary dimensions. + */ + public SampleModel getSampleModel() { + return sampleModel; + } + + /** + * Returns a SampleModel based on the settings + * encapsulated within this object. The width and height of the + * SampleModel will be set to the supplied values. + * + * @param width the desired width of the returned SampleModel. + * @param height the desired height of the returned + * SampleModel. + * + * @return a SampleModel with the given dimensions. + * + * @exception IllegalArgumentException if either width or + * height are negative or zero. + * @exception IllegalArgumentException if the product of + * width and height is greater than + * Integer.MAX_VALUE + */ + public SampleModel getSampleModel(int width, int height) { + if ((long)width*height > Integer.MAX_VALUE) { + throw new IllegalArgumentException + ("width*height > Integer.MAX_VALUE!"); + } + return sampleModel.createCompatibleSampleModel(width, height); + } + + /** + * Returns the ColorModel specified by this object. + * + * @return a ColorModel. + */ + public ColorModel getColorModel() { + return colorModel; + } + + /** + * Creates a BufferedImage with a given width and + * height according to the specification embodied in this object. + * + * @param width the desired width of the returned + * BufferedImage. + * @param height the desired height of the returned + * BufferedImage. + * + * @return a new BufferedImage + * + * @exception IllegalArgumentException if either width or + * height are negative or zero. + * @exception IllegalArgumentException if the product of + * width and height is greater than + * Integer.MAX_VALUE, or if the number of array + * elements needed to store the image is greater than + * Integer.MAX_VALUE. + */ + public BufferedImage createBufferedImage(int width, int height) { + try { + SampleModel sampleModel = getSampleModel(width, height); + WritableRaster raster = + Raster.createWritableRaster(sampleModel, + new Point(0, 0)); + return new BufferedImage(colorModel, raster, + colorModel.isAlphaPremultiplied(), + new Hashtable()); + } catch (NegativeArraySizeException e) { + // Exception most likely thrown from a DataBuffer constructor + throw new IllegalArgumentException + ("Array size > Integer.MAX_VALUE!"); + } + } + + /** + * Returns true if the given Object is + * an ImageTypeSpecifier and has a + * SampleModel and ColorModel that are + * equal to those of this object. + * + * @param o the Object to be compared for equality. + * + * @return true if the given object is an equivalent + * ImageTypeSpecifier. + */ + public boolean equals(Object o) { + if ((o == null) || !(o instanceof ImageTypeSpecifier)) { + return false; + } + + ImageTypeSpecifier that = (ImageTypeSpecifier)o; + return (colorModel.equals(that.colorModel)) && + (sampleModel.equals(that.sampleModel)); + } + + /** + * Returns the hash code for this ImageTypeSpecifier. + * + * @return a hash code for this ImageTypeSpecifier + */ + public int hashCode() { + return (9 * colorModel.hashCode()) + (14 * sampleModel.hashCode()); + } + + private static ImageTypeSpecifier getSpecifier(int type) { + if (BISpecifier[type] == null) { + BISpecifier[type] = createSpecifier(type); + } + return BISpecifier[type]; + } + + private static ImageTypeSpecifier createSpecifier(int type) { + switch(type) { + case BufferedImage.TYPE_INT_RGB: + return createPacked(sRGB, + 0x00ff0000, + 0x0000ff00, + 0x000000ff, + 0x0, + DataBuffer.TYPE_INT, + false); + + case BufferedImage.TYPE_INT_ARGB: + return createPacked(sRGB, + 0x00ff0000, + 0x0000ff00, + 0x000000ff, + 0xff000000, + DataBuffer.TYPE_INT, + false); + + case BufferedImage.TYPE_INT_ARGB_PRE: + return createPacked(sRGB, + 0x00ff0000, + 0x0000ff00, + 0x000000ff, + 0xff000000, + DataBuffer.TYPE_INT, + true); + + case BufferedImage.TYPE_INT_BGR: + return createPacked(sRGB, + 0x000000ff, + 0x0000ff00, + 0x00ff0000, + 0x0, + DataBuffer.TYPE_INT, + false); + + case BufferedImage.TYPE_3BYTE_BGR: + return createInterleaved(sRGB, + new int[] { 2, 1, 0 }, + DataBuffer.TYPE_BYTE, + false, + false); + + case BufferedImage.TYPE_4BYTE_ABGR: + return createInterleaved(sRGB, + new int[] { 3, 2, 1, 0 }, + DataBuffer.TYPE_BYTE, + true, + false); + + case BufferedImage.TYPE_4BYTE_ABGR_PRE: + return createInterleaved(sRGB, + new int[] { 3, 2, 1, 0 }, + DataBuffer.TYPE_BYTE, + true, + true); + + case BufferedImage.TYPE_USHORT_565_RGB: + return createPacked(sRGB, + 0xF800, + 0x07E0, + 0x001F, + 0x0, + DataBuffer.TYPE_USHORT, + false); + + case BufferedImage.TYPE_USHORT_555_RGB: + return createPacked(sRGB, + 0x7C00, + 0x03E0, + 0x001F, + 0x0, + DataBuffer.TYPE_USHORT, + false); + + case BufferedImage.TYPE_BYTE_GRAY: + return createGrayscale(8, + DataBuffer.TYPE_BYTE, + false); + + case BufferedImage.TYPE_USHORT_GRAY: + return createGrayscale(16, + DataBuffer.TYPE_USHORT, + false); + + case BufferedImage.TYPE_BYTE_BINARY: + return createGrayscale(1, + DataBuffer.TYPE_BYTE, + false); + + case BufferedImage.TYPE_BYTE_INDEXED: + { + + BufferedImage bi = + new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED); + IndexColorModel icm = (IndexColorModel)bi.getColorModel(); + int mapSize = icm.getMapSize(); + byte[] redLUT = new byte[mapSize]; + byte[] greenLUT = new byte[mapSize]; + byte[] blueLUT = new byte[mapSize]; + byte[] alphaLUT = new byte[mapSize]; + + icm.getReds(redLUT); + icm.getGreens(greenLUT); + icm.getBlues(blueLUT); + icm.getAlphas(alphaLUT); + + return createIndexed(redLUT, greenLUT, blueLUT, alphaLUT, + 8, + DataBuffer.TYPE_BYTE); + } + default: + throw new IllegalArgumentException("Invalid BufferedImage type!"); + } + } + +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/ImageWriter.java b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageWriter.java index 54b4c93f8..8b020a90b 100644 --- a/sources/net.sf.j2s.java.core/src/javax/imageio/ImageWriter.java +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/ImageWriter.java @@ -1,26 +1,58 @@ +/* + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package javax.imageio; +import java.awt.Dimension; +import java.awt.Rectangle; import java.awt.image.BufferedImage; +import java.awt.image.Raster; import java.awt.image.RenderedImage; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; -import java.util.Hashtable; +import java.util.List; import java.util.Locale; -import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import javax.imageio.event.IIOWriteProgressListener; +import javax.imageio.event.IIOWriteWarningListener; import javax.imageio.metadata.IIOMetadata; - -import javajs.api.GenericImageEncoder; -import javajs.util.OC; -import javajs.util.PT; -import swingjs.JSToolkit; -import swingjs.JSUtil; -import swingjs.api.Interface; +import javax.imageio.spi.ImageWriterSpi; /** - * SwingJS implementation using output channels (javajs.util.OC) - * to create PNG, GIF, JPG, or JPG64 image data. + * An abstract superclass for encoding and writing images. This class + * must be subclassed by classes that write out images in the context + * of the Java Image I/O framework. + * + *

ImageWriter objects are normally instantiated by + * the service provider class for the specific format. Service + * provider classes are registered with the IIORegistry, + * which uses them for format recognition and presentation of + * available format readers and writers. + * + *

* * @see ImageReader * @see ImageWriteParam @@ -28,231 +60,1980 @@ * @see javax.imageio.spi.ImageWriterSpi * */ -public abstract class ImageWriter { +public abstract class ImageWriter implements ImageTranscoder { - public abstract void write(IIOMetadata streamMetadata, - IIOImage image, - ImageWriteParam param) throws IOException; - - public abstract ImageWriteParam getDefaultWriteParam(); - - - protected Map params = new Hashtable(); - - - protected Locale locale; - - /** - * Create an ImageWriter that will be set with "image" and "type" later - * - */ - public ImageWriter() { - // allows passing a Hashtable with all information at once. - } - - public void setImage(RenderedImage im) { - params.put("image", (BufferedImage) im); - } - - - protected void setMetaData(Object streamMetadata) { - // not implemented - } - - protected boolean write(RenderedImage im, String fileName, OutputStream out) { - try { - if (out == null && fileName != null) - out = new OC().setParams(null, fileName, false, null); - setOutput(out); - write(null, new IIOImage(im, null, null), null); - } catch (IOException e) { - return false; - } - return true; - } - - public void setOutput(Object out) { - OC outputChannel = null; - if (out instanceof OC) { - outputChannel = (OC) out; - } else if (out instanceof FileOutputStream){ - /** - * @j2sNative - * outputChannel = out.out; - */ - } else { - outputChannel = new OC().setParams(null, null, false, (OutputStream) out); - } - params.put("outputChannel", outputChannel); - } - - protected void write(IIOImage image) throws IOException { - params.put("image", image.image); - try { - writeImage(params); - } catch (Exception e) { - if (e instanceof IOException) - throw new IOException(((IOException) e).getMessage()); - e.printStackTrace(); - } - } - - protected boolean writeToFileOrStream(String fileName, OutputStream out) { - OC outputChannel = (OC) params.get("outputChannel"); - if (out != null) { - outputChannel = new OC(); - outputChannel.setParams(null, fileName, false, out); - params.put("outputChannel", outputChannel); - } - if (outputChannel != null) - fileName = outputChannel.getFileName(); - try { - Object o = writeImage(params); - if (o instanceof String) { - System.out.println("ImageWriter: " + o); - return false; - } - if (out == null && o != null) - JSUtil.saveFile(fileName, o, (String) params.get("mimeType"), null); - } catch (Exception e) { - return false; - } - return true; - - } - - /** - * Write a PNG, GIF, JPG, or JPG64 image to a file or output channel (javajs.util.OC) or return its bytes - * starting with an image or int[] RGB buffer; allows for appending application data with a predefined - * application-specific 9-character prefix and any byte data desired. - * - * @param params - * {type:"PNG|GIF|JPEG|JPG|JPG64", transparentColor:int, fileName, image, quality - * rbgbuf+width+height, pngAppData+pngAppPrefix} - * @return String error message or byte[] if fileName and outputChannel are null or null if successful - * @throws Exception - */ - public Object writeImage(Map params) throws Exception { - BufferedImage image = (BufferedImage) params.get("image"); - if (image == null && !params.containsKey("rgbbuf")) - throw new IllegalArgumentException("image == null!"); - byte[] bytes = null; - String errMsg = null; - String type = (String) params.get("type"); - if (type == null) - type = "png"; - type = type.toUpperCase(); - String fileName = (String) params.get("fileName"); - String software = (String) params.get("software"); - if (software == null) { - software = "SwingJS"; - } - String comment = (String) params.get("comment"); - if (comment == null) - params.put("comment", software); - OC out = (OC) params.get("outputChannel"); - boolean asBytes = (out == null && fileName == null); - boolean closeChannel = (out == null && fileName != null); - boolean isOK = false; - try { - if (out == null - && (out = getOutputChannel(fileName)) == null) - return "ERROR: canceled"; - fileName = out.getFileName(); - params.put("date", JSToolkit.getDateFormat("8601")); - if (type.startsWith("JP")) { - type = PT.rep(type, "E", ""); - if (type.equals("JPG64")) { - params.put("outputChannelTemp", getOutputChannel(null)); - } - } else if (type.equals("PDF")) { - } else if (type.startsWith("PNG")) { - // if (stateData != null) { - // params.put("pngAppData", stateData); - // params.put("pngAppPrefix", "Jmol Type"); - // } - } - String[] errRet = new String[1]; - isOK = createTheImage(image, type, out, params, errRet); - if (closeChannel) - out.closeChannel(); - if (isOK) { - if (asBytes) - bytes = out.toByteArray(); - } else { - errMsg = errRet[0]; - } - } finally { - if (bytes != null || out != null) - params.put("byteCount", Integer.valueOf(bytes != null ? bytes.length - : isOK ? out.getByteCount() : -1)); - } - return (errMsg == null ? bytes : errMsg); - } - - OC getOutputChannel(String fileName) throws IOException { - @SuppressWarnings("resource") - OC outputChannel = new OC(); - return outputChannel.setParams(null, fileName, false, null); - } - - /** - * @param objImage - * @param type - * @param out - * @param params - * @param errRet - * @return byte array if needed - */ - private boolean createTheImage(BufferedImage image, String type, OC out, - Map params, String[] errRet) { - params.put("mimeType", "image/" + type.toLowerCase()); - type = type.substring(0, 1) + type.substring(1).toLowerCase(); - GenericImageEncoder ie = (GenericImageEncoder) Interface.getInstance( - "javajs.img." + type + "Encoder", true); - if (ie == null) { - errRet[0] = "Image encoder type " + type + " not available"; - return false; - } - boolean doClose = true; - int[] pixels; - int w, h; - try { - - // finish up with the image parameters, - // either image or rgbbuf+width+height - - if (image == null) { - w = ((Integer) params.get("width")).intValue(); - h = ((Integer) params.get("height")).intValue(); - pixels = (int[]) params.get("rgbbuf"); - } else { - w = image.getWidth(); - h = image.getHeight(); - pixels = image.getRangeRGB(0, 0, w, h, new int[w * h], 0, w); - } - params.put("imageWidth", Integer.valueOf(w)); - params.put("imageHeight", Integer.valueOf(h)); - params.put("imagePixels", pixels); - - // now create the image using these parameters - - doClose = ie.createImage(type, out, params); - - } catch (Exception e) { - errRet[0] = e.toString(); - out.cancel(); - doClose = true; - } finally { - if (doClose) - out.closeChannel(); - } - return (errRet[0] == null); - } + /** + * The ImageWriterSpi that instantiated this object, + * or null if its identity is not known or none + * exists. By default it is initialized to null. + */ + protected ImageWriterSpi originatingProvider = null; + /** + * The ImageOutputStream or other Object + * set by setOutput and retrieved by + * getOutput. By default it is initialized to + * null. + */ + protected Object output = null; + + /** + * An array of Locales that may be used to localize + * warning messages and compression setting values, or + * null if localization is not supported. By default + * it is initialized to null. + */ + protected Locale[] availableLocales = null; + + /** + * The current Locale to be used for localization, or + * null if none has been set. By default it is + * initialized to null. + */ + protected Locale locale = null; + + /** + * A List of currently registered + * IIOWriteWarningListeners, initialized by default to + * null, which is synonymous with an empty + * List. + */ + protected List warningListeners = null; + + /** + * A List of Locales, one for each + * element of warningListeners, initialized by default + * null, which is synonymous with an empty + * List. + */ + protected List warningLocales = null; + + /** + * A List of currently registered + * IIOWriteProgressListeners, initialized by default + * null, which is synonymous with an empty + * List. + */ + protected List progressListeners = null; + + /** + * If true, the current write operation should be + * aborted. + */ + private boolean abortFlag = false; + + /** + * Constructs an ImageWriter and sets its + * originatingProvider instance variable to the + * supplied value. + * + *

Subclasses that make use of extensions should provide a + * constructor with signature (ImageWriterSpi, + * Object) in order to retrieve the extension object. If + * the extension object is unsuitable, an + * IllegalArgumentException should be thrown. + * + * @param originatingProvider the ImageWriterSpi that + * is constructing this object, or null. + */ + protected ImageWriter(ImageWriterSpi originatingProvider) { + this.originatingProvider = originatingProvider; + } + + /** + * Returns the ImageWriterSpi object that created + * this ImageWriter, or null if this + * object was not created through the IIORegistry. + * + *

The default implementation returns the value of the + * originatingProvider instance variable. + * + * @return an ImageWriterSpi, or null. + * + * @see ImageWriterSpi + */ + public ImageWriterSpi getOriginatingProvider() { + return originatingProvider; + } + + /** + * Sets the destination to the given + * ImageOutputStream or other Object. + * The destination is assumed to be ready to accept data, and will + * not be closed at the end of each write. This allows distributed + * imaging applications to transmit a series of images over a + * single network connection. If output is + * null, any currently set output will be removed. + * + *

If output is an + * ImageOutputStream, calls to the + * write, writeToSequence, and + * prepareWriteEmpty/endWriteEmpty + * methods will preserve the existing contents of the stream. + * Other write methods, such as writeInsert, + * replaceStreamMetadata, + * replaceImageMetadata, replacePixels, + * prepareInsertEmpty/endInsertEmpty, + * and endWriteSequence, require the full contents + * of the stream to be readable and writable, and may alter any + * portion of the stream. + * + *

Use of a general Object other than an + * ImageOutputStream is intended for writers that + * interact directly with an output device or imaging protocol. + * The set of legal classes is advertised by the writer's service + * provider's getOutputTypes method; most writers + * will return a single-element array containing only + * ImageOutputStream.class to indicate that they + * accept only an ImageOutputStream. + * + *

The default implementation sets the output + * instance variable to the value of output after + * checking output against the set of classes + * advertised by the originating provider, if there is one. + * + * @param output the ImageOutputStream or other + * Object to use for future writing. + * + * @exception IllegalArgumentException if output is + * not an instance of one of the classes returned by the + * originating service provider's getOutputTypes + * method. + * + * @see #getOutput + */ + public void setOutput(Object output) { + if (output != null) { + ImageWriterSpi provider = getOriginatingProvider(); + if (provider != null) { + Class[] classes = provider.getOutputTypes(); + boolean found = false; + for (int i = 0; i < classes.length; i++) { + if (classes[i].isInstance(output)) { + found = true; + break; + } + } + if (!found) { + throw new IllegalArgumentException("Illegal output type!"); + } + } + } + + this.output = output; + } + + /** + * Returns the ImageOutputStream or other + * Object set by the most recent call to the + * setOutput method. If no destination has been + * set, null is returned. + * + *

The default implementation returns the value of the + * output instance variable. + * + * @return the Object that was specified using + * setOutput, or null. + * + * @see #setOutput + */ + public Object getOutput() { + return output; + } + + // Localization + + /** + * Returns an array of Locales that may be used to + * localize warning listeners and compression settings. A return + * value of null indicates that localization is not + * supported. + * + *

The default implementation returns a clone of the + * availableLocales instance variable if it is + * non-null, or else returns null. + * + * @return an array of Locales that may be used as + * arguments to setLocale, or null. + */ + public Locale[] getAvailableLocales() { + return (availableLocales == null) ? + null : (Locale[])availableLocales.clone(); + } + + /** + * Sets the current Locale of this + * ImageWriter to the given value. A value of + * null removes any previous setting, and indicates + * that the writer should localize as it sees fit. + * + *

The default implementation checks locale + * against the values returned by + * getAvailableLocales, and sets the + * locale instance variable if it is found. If + * locale is null, the instance variable + * is set to null without any checking. + * + * @param locale the desired Locale, or + * null. + * + * @exception IllegalArgumentException if locale is + * non-null but is not one of the values returned by + * getAvailableLocales. + * + * @see #getLocale + */ + public void setLocale(Locale locale) { + if (locale != null) { + Locale[] locales = getAvailableLocales(); + boolean found = false; + if (locales != null) { + for (int i = 0; i < locales.length; i++) { + if (locale.equals(locales[i])) { + found = true; + break; + } + } + } + if (!found) { + throw new IllegalArgumentException("Invalid locale!"); + } + } + this.locale = locale; + } + + /** + * Returns the currently set Locale, or + * null if none has been set. + * + *

The default implementation returns the value of the + * locale instance variable. + * + * @return the current Locale, or null. + * + * @see #setLocale + */ public Locale getLocale() { return locale; } - + // Write params + + /** + * Returns a new ImageWriteParam object of the + * appropriate type for this file format containing default + * values, that is, those values that would be used + * if no ImageWriteParam object were specified. This + * is useful as a starting point for tweaking just a few parameters + * and otherwise leaving the default settings alone. + * + *

The default implementation constructs and returns a new + * ImageWriteParam object that does not allow tiling, + * progressive encoding, or compression, and that will be + * localized for the current Locale (i.e., + * what you would get by calling new + * ImageWriteParam(getLocale()). + * + *

Individual plug-ins may return an instance of + * ImageWriteParam with additional optional features + * enabled, or they may return an instance of a plug-in specific + * subclass of ImageWriteParam. + * + * @return a new ImageWriteParam object containing + * default values. + */ + public ImageWriteParam getDefaultWriteParam() { + return new ImageWriteParam(getLocale()); + } + + // Metadata + + /** + * Returns an IIOMetadata object containing default + * values for encoding a stream of images. The contents of the + * object may be manipulated using either the XML tree structure + * returned by the IIOMetadata.getAsTree method, an + * IIOMetadataController object, or via plug-in + * specific interfaces, and the resulting data supplied to one of + * the write methods that take a stream metadata + * parameter. + * + *

An optional ImageWriteParam may be supplied + * for cases where it may affect the structure of the stream + * metadata. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

Writers that do not make use of stream metadata + * (e.g., writers for single-image formats) should return + * null. + * + * @param param an ImageWriteParam that will be used to + * encode the image, or null. + * + * @return an IIOMetadata object. + */ + public abstract IIOMetadata + getDefaultStreamMetadata(ImageWriteParam param); + + /** + * Returns an IIOMetadata object containing default + * values for encoding an image of the given type. The contents + * of the object may be manipulated using either the XML tree + * structure returned by the IIOMetadata.getAsTree + * method, an IIOMetadataController object, or via + * plug-in specific interfaces, and the resulting data supplied to + * one of the write methods that take a stream + * metadata parameter. + * + *

An optional ImageWriteParam may be supplied + * for cases where it may affect the structure of the image + * metadata. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + * @param imageType an ImageTypeSpecifier indicating the + * format of the image to be written later. + * @param param an ImageWriteParam that will be used to + * encode the image, or null. + * + * @return an IIOMetadata object. + */ + public abstract IIOMetadata + getDefaultImageMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param); + + // comment inherited + @Override + public abstract IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param); + + // comment inherited + @Override + public abstract IIOMetadata + convertImageMetadata(IIOMetadata inData, + ImageTypeSpecifier imageType, + ImageWriteParam param); + + // Thumbnails + + /** + * Returns the number of thumbnails supported by the format being + * written, given the image type and any additional write + * parameters and metadata objects that will be used during + * encoding. A return value of -1 indicates that + * insufficient information is available. + * + *

An ImageWriteParam may optionally be supplied + * for cases where it may affect thumbnail handling. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

The default implementation returns 0. + * + * @param imageType an ImageTypeSpecifier indicating + * the type of image to be written, or null. + * @param param the ImageWriteParam that will be used for + * writing, or null. + * @param streamMetadata an IIOMetadata object that will + * be used for writing, or null. + * @param imageMetadata an IIOMetadata object that will + * be used for writing, or null. + * + * @return the number of thumbnails that may be written given the + * supplied parameters, or -1 if insufficient + * information is available. + */ + public int getNumThumbnailsSupported(ImageTypeSpecifier imageType, + ImageWriteParam param, + IIOMetadata streamMetadata, + IIOMetadata imageMetadata) { + return 0; + } + + /** + * Returns an array of Dimensions indicating the + * legal size ranges for thumbnail images as they will be encoded + * in the output file or stream. This information is merely + * advisory; the writer will resize any supplied thumbnails as + * necessary. + * + *

The information is returned as a set of pairs; the first + * element of a pair contains an (inclusive) minimum width and + * height, and the second element contains an (inclusive) maximum + * width and height. Together, each pair defines a valid range of + * sizes. To specify a fixed size, the same width and height will + * appear for both elements. A return value of null + * indicates that the size is arbitrary or unknown. + * + *

An ImageWriteParam may optionally be supplied + * for cases where it may affect thumbnail handling. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

The default implementation returns null. + * + * @param imageType an ImageTypeSpecifier indicating the + * type of image to be written, or null. + * @param param the ImageWriteParam that will be used for + * writing, or null. + * @param streamMetadata an IIOMetadata object that will + * be used for writing, or null. + * @param imageMetadata an IIOMetadata object that will + * be used for writing, or null. + * + * @return an array of Dimensions with an even length + * of at least two, or null. + */ + public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType, + ImageWriteParam param, + IIOMetadata streamMetadata, + IIOMetadata imageMetadata) { + return null; + } + + /** + * Returns true if the methods that take an + * IIOImage parameter are capable of dealing with a + * Raster (as opposed to RenderedImage) + * source image. If this method returns false, then + * those methods will throw an + * UnsupportedOperationException if supplied with an + * IIOImage containing a Raster. + * + *

The default implementation returns false. + * + * @return true if Raster sources are + * supported. + */ + public boolean canWriteRasters() { + return false; + } + + /** + * Appends a complete image stream containing a single image and + * associated stream and image metadata and thumbnails to the + * output. Any necessary header information is included. If the + * output is an ImageOutputStream, its existing + * contents prior to the current seek position are not affected, + * and need not be readable or writable. + * + *

The output must have been set beforehand using the + * setOutput method. + * + *

Stream metadata may optionally be supplied; if it is + * null, default stream metadata will be used. + * + *

If canWriteRasters returns true, + * the IIOImage may contain a Raster + * source. Otherwise, it must contain a + * RenderedImage source. + * + *

The supplied thumbnails will be resized if needed, and any + * thumbnails in excess of the supported number will be ignored. + * If the format requires additional thumbnails that are not + * provided, the writer should generate them internally. + * + *

An ImageWriteParam may + * optionally be supplied to control the writing process. If + * param is null, a default write param + * will be used. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + * @param streamMetadata an IIOMetadata object representing + * stream metadata, or null to use default values. + * @param image an IIOImage object containing an + * image, thumbnails, and metadata to be written. + * @param param an ImageWriteParam, or + * null to use a default + * ImageWriteParam. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if image + * contains a Raster and canWriteRasters + * returns false. + * @exception IllegalArgumentException if image is + * null. + * @exception IOException if an error occurs during writing. + */ + public abstract void write(IIOMetadata streamMetadata, + IIOImage image, + ImageWriteParam param) throws IOException; + + /** + * Appends a complete image stream containing a single image with + * default metadata and thumbnails to the output. This method is + * a shorthand for write(null, image, null). + * + * @param image an IIOImage object containing an + * image, thumbnails, and metadata to be written. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception IllegalArgumentException if image is + * null. + * @exception UnsupportedOperationException if image + * contains a Raster and canWriteRasters + * returns false. + * @exception IOException if an error occurs during writing. + */ + public void write(IIOImage image) throws IOException { + write(null, image, null); + } + + /** + * Appends a complete image stream consisting of a single image + * with default metadata and thumbnails to the output. This + * method is a shorthand for write(null, new IIOImage(image, + * null, null), null). + * + * @param image a RenderedImage to be written. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception IllegalArgumentException if image is + * null. + * @exception IOException if an error occurs during writing. + */ + public void write(RenderedImage image) throws IOException { + write(null, new IIOImage(image, null, null), null); + } + + // Check that the output has been set, then throw an + // UnsupportedOperationException. + private void unsupported() { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + throw new UnsupportedOperationException("Unsupported write variant!"); + } + + // Sequence writes + + /** + * Returns true if the writer is able to append an + * image to an image stream that already contains header + * information and possibly prior images. + * + *

If canWriteSequence returns false, + * writeToSequence and endWriteSequence + * will throw an UnsupportedOperationException. + * + *

The default implementation returns false. + * + * @return true if images may be appended sequentially. + */ + public boolean canWriteSequence() { + return false; + } + + /** + * Prepares a stream to accept a series of subsequent + * writeToSequence calls, using the provided stream + * metadata object. The metadata will be written to the stream if + * it should precede the image data. If the argument is null, + * default stream metadata is used. + * + *

If the output is an ImageOutputStream, the existing + * contents of the output prior to the current seek position are + * flushed, and need not be readable or writable. If the format + * requires that endWriteSequence be able to rewind to + * patch up the header information, such as for a sequence of images + * in a single TIFF file, then the metadata written by this method + * must remain in a writable portion of the stream. Other formats + * may flush the stream after this method and after each image. + * + *

If canWriteSequence returns false, + * this method will throw an + * UnsupportedOperationException. + * + *

The output must have been set beforehand using either + * the setOutput method. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param streamMetadata A stream metadata object, or null. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canWriteSequence returns false. + * @exception IOException if an error occurs writing the stream + * metadata. + */ + public void prepareWriteSequence(IIOMetadata streamMetadata) + throws IOException { + unsupported(); + } + + /** + * Appends a single image and possibly associated metadata and + * thumbnails, to the output. If the output is an + * ImageOutputStream, the existing contents of the + * output prior to the current seek position may be flushed, and + * need not be readable or writable, unless the plug-in needs to + * be able to patch up the header information when + * endWriteSequence is called (e.g. TIFF). + * + *

If canWriteSequence returns false, + * this method will throw an + * UnsupportedOperationException. + * + *

The output must have been set beforehand using + * the setOutput method. + * + *

prepareWriteSequence must have been called + * beforehand, or an IllegalStateException is thrown. + * + *

If canWriteRasters returns true, + * the IIOImage may contain a Raster + * source. Otherwise, it must contain a + * RenderedImage source. + * + *

The supplied thumbnails will be resized if needed, and any + * thumbnails in excess of the supported number will be ignored. + * If the format requires additional thumbnails that are not + * provided, the writer will generate them internally. + * + *

An ImageWriteParam may optionally be supplied + * to control the writing process. If param is + * null, a default write param will be used. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param image an IIOImage object containing an + * image, thumbnails, and metadata to be written. + * @param param an ImageWriteParam, or + * null to use a default + * ImageWriteParam. + * + * @exception IllegalStateException if the output has not + * been set, or prepareWriteSequence has not been called. + * @exception UnsupportedOperationException if + * canWriteSequence returns false. + * @exception IllegalArgumentException if image is + * null. + * @exception UnsupportedOperationException if image + * contains a Raster and canWriteRasters + * returns false. + * @exception IOException if an error occurs during writing. + */ + public void writeToSequence(IIOImage image, ImageWriteParam param) + throws IOException { + unsupported(); + } + + /** + * Completes the writing of a sequence of images begun with + * prepareWriteSequence. Any stream metadata that + * should come at the end of the sequence of images is written out, + * and any header information at the beginning of the sequence is + * patched up if necessary. If the output is an + * ImageOutputStream, data through the stream metadata + * at the end of the sequence are flushed and need not be readable + * or writable. + * + *

If canWriteSequence returns false, + * this method will throw an + * UnsupportedOperationException. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @exception IllegalStateException if the output has not + * been set, or prepareWriteSequence has not been called. + * @exception UnsupportedOperationException if + * canWriteSequence returns false. + * @exception IOException if an error occurs during writing. + */ + public void endWriteSequence() throws IOException { + unsupported(); + } + + // Metadata replacement + + /** + * Returns true if it is possible to replace the + * stream metadata already present in the output. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise returns false. + * + * @return true if replacement of stream metadata is + * allowed. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception IOException if an I/O error occurs during the query. + */ + public boolean canReplaceStreamMetadata() throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + return false; + } + + /** + * Replaces the stream metadata in the output with new + * information. If the output is an + * ImageOutputStream, the prior contents of the + * stream are examined and possibly edited to make room for the + * new data. All of the prior contents of the output must be + * available for reading and writing. + * + *

If canReplaceStreamMetadata returns + * false, an + * UnsupportedOperationException will be thrown. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param streamMetadata an IIOMetadata object representing + * stream metadata, or null to use default values. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if the + * canReplaceStreamMetadata returns + * false. modes do not include + * @exception IOException if an error occurs during writing. + */ + public void replaceStreamMetadata(IIOMetadata streamMetadata) + throws IOException { + unsupported(); + } + + /** + * Returns true if it is possible to replace the + * image metadata associated with an existing image with index + * imageIndex. If this method returns + * false, a call to + * replaceImageMetadata(imageIndex) will throw an + * UnsupportedOperationException. + * + *

A writer that does not support any image metadata + * replacement may return false without performing + * bounds checking on the index. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise returns false + * without checking the value of imageIndex. + * + * @param imageIndex the index of the image whose metadata is to + * be replaced. + * + * @return true if the image metadata of the given + * image can be replaced. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception IndexOutOfBoundsException if the writer supports + * image metadata replacement in general, but + * imageIndex is less than 0 or greater than the + * largest available index. + * @exception IOException if an I/O error occurs during the query. + */ + public boolean canReplaceImageMetadata(int imageIndex) + throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + return false; + } + + /** + * Replaces the image metadata associated with an existing image. + * + *

If canReplaceImageMetadata(imageIndex) returns + * false, an + * UnsupportedOperationException will be thrown. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param imageIndex the index of the image whose metadata is to + * be replaced. + * @param imageMetadata an IIOMetadata object + * representing image metadata, or null. + * + * @exception IllegalStateException if the output has not been + * set. + * @exception UnsupportedOperationException if + * canReplaceImageMetadata returns + * false. + * @exception IndexOutOfBoundsException if imageIndex + * is less than 0 or greater than the largest available index. + * @exception IOException if an error occurs during writing. + */ + public void replaceImageMetadata(int imageIndex, + IIOMetadata imageMetadata) + throws IOException { + unsupported(); + } + + // Image insertion + + /** + * Returns true if the writer supports the insertion + * of a new image at the given index. Existing images with + * indices greater than or equal to the insertion index will have + * their indices increased by 1. A value for + * imageIndex of -1 may be used to + * signify an index one larger than the current largest index. + * + *

A writer that does not support any image insertion may + * return false without performing bounds checking on + * the index. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise returns false + * without checking the value of imageIndex. + * + * @param imageIndex the index at which the image is to be + * inserted. + * + * @return true if an image may be inserted at the + * given index. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception IndexOutOfBoundsException if the writer supports + * image insertion in general, but imageIndex is less + * than -1 or greater than the largest available index. + * @exception IOException if an I/O error occurs during the query. + */ + public boolean canInsertImage(int imageIndex) throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + return false; + } + + /** + * Inserts a new image into an existing image stream. Existing + * images with an index greater than imageIndex are + * preserved, and their indices are each increased by 1. A value + * for imageIndex of -1 may be used to signify an + * index one larger than the previous largest index; that is, it + * will cause the image to be logically appended to the end of the + * sequence. If the output is an ImageOutputStream, + * the entirety of the stream must be both readable and writeable. + * + *

If canInsertImage(imageIndex) returns + * false, an + * UnsupportedOperationException will be thrown. + * + *

An ImageWriteParam may optionally be supplied + * to control the writing process. If param is + * null, a default write param will be used. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param imageIndex the index at which to write the image. + * @param image an IIOImage object containing an + * image, thumbnails, and metadata to be written. + * @param param an ImageWriteParam, or + * null to use a default + * ImageWriteParam. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canInsertImage(imageIndex) returns false. + * @exception IllegalArgumentException if image is + * null. + * @exception IndexOutOfBoundsException if imageIndex + * is less than -1 or greater than the largest available index. + * @exception UnsupportedOperationException if image + * contains a Raster and canWriteRasters + * returns false. + * @exception IOException if an error occurs during writing. + */ + public void writeInsert(int imageIndex, + IIOImage image, + ImageWriteParam param) throws IOException { + unsupported(); + } + + // Image removal + + /** + * Returns true if the writer supports the removal + * of an existing image at the given index. Existing images with + * indices greater than the insertion index will have + * their indices decreased by 1. + * + *

A writer that does not support any image removal may + * return false without performing bounds checking on + * the index. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise returns false + * without checking the value of imageIndex. + * + * @param imageIndex the index of the image to be removed. + * + * @return true if it is possible to remove the given + * image. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception IndexOutOfBoundsException if the writer supports + * image removal in general, but imageIndex is less + * than 0 or greater than the largest available index. + * @exception IOException if an I/O error occurs during the + * query. + */ + public boolean canRemoveImage(int imageIndex) throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + return false; + } + + /** + * Removes an image from the stream. + * + *

If canRemoveImage(imageIndex) returns false, + * an UnsupportedOperationExceptionwill be thrown. + * + *

The removal may or may not cause a reduction in the actual + * file size. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param imageIndex the index of the image to be removed. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canRemoveImage(imageIndex) returns false. + * @exception IndexOutOfBoundsException if imageIndex + * is less than 0 or greater than the largest available index. + * @exception IOException if an I/O error occurs during the + * removal. + */ + public void removeImage(int imageIndex) throws IOException { + unsupported(); + } + + // Empty images + + /** + * Returns true if the writer supports the writing of + * a complete image stream consisting of a single image with + * undefined pixel values and associated metadata and thumbnails + * to the output. The pixel values may be defined by future + * calls to the replacePixels methods. If the output + * is an ImageOutputStream, its existing contents + * prior to the current seek position are not affected, and need + * not be readable or writable. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise returns false. + * + * @return true if the writing of complete image + * stream with contents to be defined later is supported. + * + * @exception IllegalStateException if the output has not been + * set. + * @exception IOException if an I/O error occurs during the + * query. + */ + public boolean canWriteEmpty() throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + return false; + } + + /** + * Begins the writing of a complete image stream, consisting of a + * single image with undefined pixel values and associated + * metadata and thumbnails, to the output. The pixel values will + * be defined by future calls to the replacePixels + * methods. If the output is an ImageOutputStream, + * its existing contents prior to the current seek position are + * not affected, and need not be readable or writable. + * + *

The writing is not complete until a call to + * endWriteEmpty occurs. Calls to + * prepareReplacePixels, replacePixels, + * and endReplacePixels may occur between calls to + * prepareWriteEmpty and endWriteEmpty. + * However, calls to prepareWriteEmpty cannot be + * nested, and calls to prepareWriteEmpty and + * prepareInsertEmpty may not be interspersed. + * + *

If canWriteEmpty returns false, + * an UnsupportedOperationException will be thrown. + * + *

An ImageWriteParam may optionally be supplied + * to control the writing process. If param is + * null, a default write param will be used. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param streamMetadata an IIOMetadata object representing + * stream metadata, or null to use default values. + * @param imageType an ImageTypeSpecifier describing + * the layout of the image. + * @param width the width of the image. + * @param height the height of the image. + * @param imageMetadata an IIOMetadata object + * representing image metadata, or null. + * @param thumbnails a List of + * BufferedImage thumbnails for this image, or + * null. + * @param param an ImageWriteParam, or + * null to use a default + * ImageWriteParam. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canWriteEmpty returns false. + * @exception IllegalStateException if a previous call to + * prepareWriteEmpty has been made without a + * corresponding call to endWriteEmpty. + * @exception IllegalStateException if a previous call to + * prepareInsertEmpty has been made without a + * corresponding call to endInsertEmpty. + * @exception IllegalArgumentException if imageType + * is null or thumbnails contains + * null references or objects other than + * BufferedImages. + * @exception IllegalArgumentException if width or height are less + * than 1. + * @exception IOException if an I/O error occurs during writing. + */ + public void prepareWriteEmpty(IIOMetadata streamMetadata, + ImageTypeSpecifier imageType, + int width, int height, + IIOMetadata imageMetadata, + List thumbnails, + ImageWriteParam param) throws IOException { + unsupported(); + } + + /** + * Completes the writing of a new image that was begun with a + * prior call to prepareWriteEmpty. + * + *

If canWriteEmpty() returns false, + * an UnsupportedOperationException will be thrown. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canWriteEmpty(imageIndex) returns + * false. + * @exception IllegalStateException if a previous call to + * prepareWriteEmpty without a corresponding call to + * endWriteEmpty has not been made. + * @exception IllegalStateException if a previous call to + * prepareInsertEmpty without a corresponding call to + * endInsertEmpty has been made. + * @exception IllegalStateException if a call to + * prepareReiplacePixels has been made without a + * matching call to endReplacePixels. + * @exception IOException if an I/O error occurs during writing. + */ + public void endWriteEmpty() throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + throw new IllegalStateException("No call to prepareWriteEmpty!"); + } + + /** + * Returns true if the writer supports the insertion + * of a new, empty image at the given index. The pixel values of + * the image are undefined, and may be specified in pieces using + * the replacePixels methods. Existing images with + * indices greater than or equal to the insertion index will have + * their indices increased by 1. A value for + * imageIndex of -1 may be used to + * signify an index one larger than the current largest index. + * + *

A writer that does not support insertion of empty images + * may return false without performing bounds + * checking on the index. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise returns false + * without checking the value of imageIndex. + * + * @param imageIndex the index at which the image is to be + * inserted. + * + * @return true if an empty image may be inserted at + * the given index. + * + * @exception IllegalStateException if the output has not been + * set. + * @exception IndexOutOfBoundsException if the writer supports + * empty image insertion in general, but imageIndex + * is less than -1 or greater than the largest available index. + * @exception IOException if an I/O error occurs during the + * query. + */ + public boolean canInsertEmpty(int imageIndex) throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + return false; + } + + /** + * Begins the insertion of a new image with undefined pixel values + * into an existing image stream. Existing images with an index + * greater than imageIndex are preserved, and their + * indices are each increased by 1. A value for + * imageIndex of -1 may be used to signify an index + * one larger than the previous largest index; that is, it will + * cause the image to be logically appended to the end of the + * sequence. If the output is an ImageOutputStream, + * the entirety of the stream must be both readable and writeable. + * + *

The image contents may be + * supplied later using the replacePixels method. + * The insertion is not complete until a call to + * endInsertEmpty occurs. Calls to + * prepareReplacePixels, replacePixels, + * and endReplacePixels may occur between calls to + * prepareInsertEmpty and + * endInsertEmpty. However, calls to + * prepareInsertEmpty cannot be nested, and calls to + * prepareWriteEmpty and + * prepareInsertEmpty may not be interspersed. + * + *

If canInsertEmpty(imageIndex) returns + * false, an + * UnsupportedOperationException will be thrown. + * + *

An ImageWriteParam may optionally be supplied + * to control the writing process. If param is + * null, a default write param will be used. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param imageIndex the index at which to write the image. + * @param imageType an ImageTypeSpecifier describing + * the layout of the image. + * @param width the width of the image. + * @param height the height of the image. + * @param imageMetadata an IIOMetadata object + * representing image metadata, or null. + * @param thumbnails a List of + * BufferedImage thumbnails for this image, or + * null. + * @param param an ImageWriteParam, or + * null to use a default + * ImageWriteParam. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canInsertEmpty(imageIndex) returns + * false. + * @exception IndexOutOfBoundsException if imageIndex + * is less than -1 or greater than the largest available index. + * @exception IllegalStateException if a previous call to + * prepareInsertEmpty has been made without a + * corresponding call to endInsertEmpty. + * @exception IllegalStateException if a previous call to + * prepareWriteEmpty has been made without a + * corresponding call to endWriteEmpty. + * @exception IllegalArgumentException if imageType + * is null or thumbnails contains + * null references or objects other than + * BufferedImages. + * @exception IllegalArgumentException if width or height are less + * than 1. + * @exception IOException if an I/O error occurs during writing. + */ + public void prepareInsertEmpty(int imageIndex, + ImageTypeSpecifier imageType, + int width, int height, + IIOMetadata imageMetadata, + List thumbnails, + ImageWriteParam param) throws IOException { + unsupported(); + } + + /** + * Completes the insertion of a new image that was begun with a + * prior call to prepareInsertEmpty. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canInsertEmpty(imageIndex) returns + * false. + * @exception IllegalStateException if a previous call to + * prepareInsertEmpty without a corresponding call to + * endInsertEmpty has not been made. + * @exception IllegalStateException if a previous call to + * prepareWriteEmpty without a corresponding call to + * endWriteEmpty has been made. + * @exception IllegalStateException if a call to + * prepareReplacePixels has been made without a + * matching call to endReplacePixels. + * @exception IOException if an I/O error occurs during writing. + */ + public void endInsertEmpty() throws IOException { + unsupported(); + } + + // Pixel replacement + + /** + * Returns true if the writer allows pixels of the + * given image to be replaced using the replacePixels + * methods. + * + *

A writer that does not support any pixel replacement may + * return false without performing bounds checking on + * the index. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise returns false + * without checking the value of imageIndex. + * + * @param imageIndex the index of the image whose pixels are to be + * replaced. + * + * @return true if the pixels of the given + * image can be replaced. + * + * @exception IllegalStateException if the output has not been + * set. + * @exception IndexOutOfBoundsException if the writer supports + * pixel replacement in general, but imageIndex is + * less than 0 or greater than the largest available index. + * @exception IOException if an I/O error occurs during the query. + */ + public boolean canReplacePixels(int imageIndex) throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + return false; + } + + /** + * Prepares the writer to handle a series of calls to the + * replacePixels methods. The affected pixel area + * will be clipped against the supplied + * + *

If canReplacePixels returns + * false, and + * UnsupportedOperationException will be thrown. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param imageIndex the index of the image whose pixels are to be + * replaced. + * @param region a Rectangle that will be used to clip + * future pixel regions. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canReplacePixels(imageIndex) returns + * false. + * @exception IndexOutOfBoundsException if imageIndex + * is less than 0 or greater than the largest available index. + * @exception IllegalStateException if there is a previous call to + * prepareReplacePixels without a matching call to + * endReplacePixels (i.e., nesting is not + * allowed). + * @exception IllegalArgumentException if region is + * null or has a width or height less than 1. + * @exception IOException if an I/O error occurs during the + * preparation. + */ + public void prepareReplacePixels(int imageIndex, + Rectangle region) throws IOException { + unsupported(); + } + + /** + * Replaces a portion of an image already present in the output + * with a portion of the given image. The image data must match, + * or be convertible to, the image layout of the existing image. + * + *

The destination region is specified in the + * param argument, and will be clipped to the image + * boundaries and the region supplied to + * prepareReplacePixels. At least one pixel of the + * source must not be clipped, or an exception is thrown. + * + *

An ImageWriteParam may optionally be supplied + * to control the writing process. If param is + * null, a default write param will be used. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

This method may only be called after a call to + * prepareReplacePixels, or else an + * IllegalStateException will be thrown. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param image a RenderedImage containing source + * pixels. + * @param param an ImageWriteParam, or + * null to use a default + * ImageWriteParam. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canReplacePixels(imageIndex) returns + * false. + * @exception IllegalStateException if there is no previous call to + * prepareReplacePixels without a matching call to + * endReplacePixels. + * @exception IllegalArgumentException if any of the following are true: + *

    + *
  • image is null. + *
  • param is null. + *
  • the intersected region does not contain at least one pixel. + *
  • the layout of image does not match, or this + * writer cannot convert it to, the existing image layout. + *
+ * @exception IOException if an I/O error occurs during writing. + */ + public void replacePixels(RenderedImage image, ImageWriteParam param) + throws IOException { + unsupported(); + } + + /** + * Replaces a portion of an image already present in the output + * with a portion of the given Raster. The image + * data must match, or be convertible to, the image layout of the + * existing image. + * + *

An ImageWriteParam may optionally be supplied + * to control the writing process. If param is + * null, a default write param will be used. + * + *

The destination region is specified in the + * param argument, and will be clipped to the image + * boundaries and the region supplied to + * prepareReplacePixels. At least one pixel of the + * source must not be clipped, or an exception is thrown. + * + *

If the supplied ImageWriteParam contains + * optional setting values not supported by this writer (e.g. + * progressive encoding or any format-specific settings), they + * will be ignored. + * + *

This method may only be called after a call to + * prepareReplacePixels, or else an + * IllegalStateException will be thrown. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @param raster a Raster containing source + * pixels. + * @param param an ImageWriteParam, or + * null to use a default + * ImageWriteParam. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canReplacePixels(imageIndex) returns + * false. + * @exception IllegalStateException if there is no previous call to + * prepareReplacePixels without a matching call to + * endReplacePixels. + * @exception UnsupportedOperationException if + * canWriteRasters returns false. + * @exception IllegalArgumentException if any of the following are true: + *

    + *
  • raster is null. + *
  • param is null. + *
  • the intersected region does not contain at least one pixel. + *
  • the layout of raster does not match, or this + * writer cannot convert it to, the existing image layout. + *
+ * @exception IOException if an I/O error occurs during writing. + */ + public void replacePixels(Raster raster, ImageWriteParam param) + throws IOException { + unsupported(); + } + + /** + * Terminates a sequence of calls to replacePixels. + * + *

If canReplacePixels returns + * false, and + * UnsupportedOperationException will be thrown. + * + *

The default implementation throws an + * IllegalStateException if the output is + * null, and otherwise throws an + * UnsupportedOperationException. + * + * @exception IllegalStateException if the output has not + * been set. + * @exception UnsupportedOperationException if + * canReplacePixels(imageIndex) returns + * false. + * @exception IllegalStateException if there is no previous call + * to prepareReplacePixels without a matching call to + * endReplacePixels. + * @exception IOException if an I/O error occurs during writing. + */ + public void endReplacePixels() throws IOException { + unsupported(); + } + + // Abort + + /** + * Requests that any current write operation be aborted. The + * contents of the output following the abort will be undefined. + * + *

Writers should call clearAbortRequest at the + * beginning of each write operation, and poll the value of + * abortRequested regularly during the write. + */ + public synchronized void abort() { + this.abortFlag = true; + } + + /** + * Returns true if a request to abort the current + * write operation has been made since the writer was instantiated or + * clearAbortRequest was called. + * + * @return true if the current write operation should + * be aborted. + * + * @see #abort + * @see #clearAbortRequest + */ + protected synchronized boolean abortRequested() { + return this.abortFlag; + } + + /** + * Clears any previous abort request. After this method has been + * called, abortRequested will return + * false. + * + * @see #abort + * @see #abortRequested + */ + protected synchronized void clearAbortRequest() { + this.abortFlag = false; + } + + // Listeners + + /** + * Adds an IIOWriteWarningListener to the list of + * registered warning listeners. If listener is + * null, no exception will be thrown and no action + * will be taken. Messages sent to the given listener will be + * localized, if possible, to match the current + * Locale. If no Locale has been set, + * warning messages may be localized as the writer sees fit. + * + * @param listener an IIOWriteWarningListener to be + * registered. + * + * @see #removeIIOWriteWarningListener + */ + public void addIIOWriteWarningListener(IIOWriteWarningListener listener) { + if (listener == null) { + return; + } + warningListeners = ImageReader.addToList(warningListeners, listener); + warningLocales = ImageReader.addToList(warningLocales, getLocale()); + } + + /** + * Removes an IIOWriteWarningListener from the list + * of registered warning listeners. If the listener was not + * previously registered, or if listener is + * null, no exception will be thrown and no action + * will be taken. + * + * @param listener an IIOWriteWarningListener to be + * deregistered. + * + * @see #addIIOWriteWarningListener + */ + public + void removeIIOWriteWarningListener(IIOWriteWarningListener listener) { + if (listener == null || warningListeners == null) { + return; + } + int index = warningListeners.indexOf(listener); + if (index != -1) { + warningListeners.remove(index); + warningLocales.remove(index); + if (warningListeners.size() == 0) { + warningListeners = null; + warningLocales = null; + } + } + } + + /** + * Removes all currently registered + * IIOWriteWarningListener objects. + * + *

The default implementation sets the + * warningListeners and warningLocales + * instance variables to null. + */ + public void removeAllIIOWriteWarningListeners() { + this.warningListeners = null; + this.warningLocales = null; + } + + /** + * Adds an IIOWriteProgressListener to the list of + * registered progress listeners. If listener is + * null, no exception will be thrown and no action + * will be taken. + * + * @param listener an IIOWriteProgressListener to be + * registered. + * + * @see #removeIIOWriteProgressListener + */ + public void + addIIOWriteProgressListener(IIOWriteProgressListener listener) { + if (listener == null) { + return; + } + progressListeners = ImageReader.addToList(progressListeners, listener); + } + + /** + * Removes an IIOWriteProgressListener from the list + * of registered progress listeners. If the listener was not + * previously registered, or if listener is + * null, no exception will be thrown and no action + * will be taken. + * + * @param listener an IIOWriteProgressListener to be + * deregistered. + * + * @see #addIIOWriteProgressListener + */ + public void + removeIIOWriteProgressListener(IIOWriteProgressListener listener) { + if (listener == null || progressListeners == null) { + return; + } + progressListeners = + ImageReader.removeFromList(progressListeners, listener); + } + + /** + * Removes all currently registered + * IIOWriteProgressListener objects. + * + *

The default implementation sets the + * progressListeners instance variable to + * null. + */ + public void removeAllIIOWriteProgressListeners() { + this.progressListeners = null; + } + + /** + * Broadcasts the start of an image write to all registered + * IIOWriteProgressListeners by calling their + * imageStarted method. Subclasses may use this + * method as a convenience. + * + * @param imageIndex the index of the image about to be written. + */ + protected void processImageStarted(int imageIndex) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteProgressListener listener = + (IIOWriteProgressListener)progressListeners.get(i); + listener.imageStarted(this, imageIndex); + } + } + + /** + * Broadcasts the current percentage of image completion to all + * registered IIOWriteProgressListeners by calling + * their imageProgress method. Subclasses may use + * this method as a convenience. + * + * @param percentageDone the current percentage of completion, + * as a float. + */ + protected void processImageProgress(float percentageDone) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteProgressListener listener = + (IIOWriteProgressListener)progressListeners.get(i); + listener.imageProgress(this, percentageDone); + } + } + + /** + * Broadcasts the completion of an image write to all registered + * IIOWriteProgressListeners by calling their + * imageComplete method. Subclasses may use this + * method as a convenience. + */ + protected void processImageComplete() { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteProgressListener listener = + (IIOWriteProgressListener)progressListeners.get(i); + listener.imageComplete(this); + } + } + + /** + * Broadcasts the start of a thumbnail write to all registered + * IIOWriteProgressListeners by calling their + * thumbnailStarted method. Subclasses may use this + * method as a convenience. + * + * @param imageIndex the index of the image associated with the + * thumbnail. + * @param thumbnailIndex the index of the thumbnail. + */ + protected void processThumbnailStarted(int imageIndex, + int thumbnailIndex) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteProgressListener listener = + (IIOWriteProgressListener)progressListeners.get(i); + listener.thumbnailStarted(this, imageIndex, thumbnailIndex); + } + } + + /** + * Broadcasts the current percentage of thumbnail completion to + * all registered IIOWriteProgressListeners by calling + * their thumbnailProgress method. Subclasses may + * use this method as a convenience. + * + * @param percentageDone the current percentage of completion, + * as a float. + */ + protected void processThumbnailProgress(float percentageDone) { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteProgressListener listener = + (IIOWriteProgressListener)progressListeners.get(i); + listener.thumbnailProgress(this, percentageDone); + } + } + + /** + * Broadcasts the completion of a thumbnail write to all registered + * IIOWriteProgressListeners by calling their + * thumbnailComplete method. Subclasses may use this + * method as a convenience. + */ + protected void processThumbnailComplete() { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteProgressListener listener = + (IIOWriteProgressListener)progressListeners.get(i); + listener.thumbnailComplete(this); + } + } + + /** + * Broadcasts that the write has been aborted to all registered + * IIOWriteProgressListeners by calling their + * writeAborted method. Subclasses may use this + * method as a convenience. + */ + protected void processWriteAborted() { + if (progressListeners == null) { + return; + } + int numListeners = progressListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteProgressListener listener = + (IIOWriteProgressListener)progressListeners.get(i); + listener.writeAborted(this); + } + } + + /** + * Broadcasts a warning message to all registered + * IIOWriteWarningListeners by calling their + * warningOccurred method. Subclasses may use this + * method as a convenience. + * + * @param imageIndex the index of the image on which the warning + * occurred. + * @param warning the warning message. + * + * @exception IllegalArgumentException if warning + * is null. + */ + protected void processWarningOccurred(int imageIndex, + String warning) { + if (warningListeners == null) { + return; + } + if (warning == null) { + throw new IllegalArgumentException("warning == null!"); + } + int numListeners = warningListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteWarningListener listener = + (IIOWriteWarningListener)warningListeners.get(i); + + listener.warningOccurred(this, imageIndex, warning); + } + } + + /** + * Broadcasts a localized warning message to all registered + * IIOWriteWarningListeners by calling their + * warningOccurred method with a string taken + * from a ResourceBundle. Subclasses may use this + * method as a convenience. + * + * @param imageIndex the index of the image on which the warning + * occurred. + * @param baseName the base name of a set of + * ResourceBundles containing localized warning + * messages. + * @param keyword the keyword used to index the warning message + * within the set of ResourceBundles. + * + * @exception IllegalArgumentException if baseName + * is null. + * @exception IllegalArgumentException if keyword + * is null. + * @exception IllegalArgumentException if no appropriate + * ResourceBundle may be located. + * @exception IllegalArgumentException if the named resource is + * not found in the located ResourceBundle. + * @exception IllegalArgumentException if the object retrieved + * from the ResourceBundle is not a + * String. + */ + protected void processWarningOccurred(int imageIndex, + String baseName, + String keyword) { + if (warningListeners == null) { + return; + } + if (baseName == null) { + throw new IllegalArgumentException("baseName == null!"); + } + if (keyword == null) { + throw new IllegalArgumentException("keyword == null!"); + } + int numListeners = warningListeners.size(); + for (int i = 0; i < numListeners; i++) { + IIOWriteWarningListener listener = + (IIOWriteWarningListener)warningListeners.get(i); + Locale locale = (Locale)warningLocales.get(i); + if (locale == null) { + locale = Locale.getDefault(); + } + + /** + * If an applet supplies an implementation of ImageWriter and + * resource bundles, then the resource bundle will need to be + * accessed via the applet class loader. So first try the context + * class loader to locate the resource bundle. + * If that throws MissingResourceException, then try the + * system class loader. + */ + ClassLoader loader = (ClassLoader) + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + @Override + public Object run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + + ResourceBundle bundle = null; + try { + bundle = ResourceBundle.getBundle(baseName, locale, loader); + } catch (MissingResourceException mre) { + try { + bundle = ResourceBundle.getBundle(baseName, locale); + } catch (MissingResourceException mre1) { + throw new IllegalArgumentException("Bundle not found!"); + } + } + + String warning = null; + try { + warning = bundle.getString(keyword); + } catch (ClassCastException cce) { + throw new IllegalArgumentException("Resource is not a String!"); + } catch (MissingResourceException mre) { + throw new IllegalArgumentException("Resource is missing!"); + } + + listener.warningOccurred(this, imageIndex, warning); + } + } + + // State management + + /** + * Restores the ImageWriter to its initial state. + * + *

The default implementation calls + * setOutput(null), setLocale(null), + * removeAllIIOWriteWarningListeners(), + * removeAllIIOWriteProgressListeners(), and + * clearAbortRequest. + */ + public void reset() { + setOutput(null); + setLocale(null); + removeAllIIOWriteWarningListeners(); + removeAllIIOWriteProgressListeners(); + clearAbortRequest(); + } + + /** + * Allows any resources held by this object to be released. The + * result of calling any other method (other than + * finalize) subsequent to a call to this method + * is undefined. + * + *

It is important for applications to call this method when they + * know they will no longer be using this ImageWriter. + * Otherwise, the writer may continue to hold on to resources + * indefinitely. + * + *

The default implementation of this method in the superclass does + * nothing. Subclass implementations should ensure that all resources, + * especially native resources, are released. + */ + public void dispose() { + } } diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadProgressListener.java b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadProgressListener.java new file mode 100644 index 000000000..d98856b5c --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadProgressListener.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.event; + +import java.util.EventListener; +import javax.imageio.ImageReader; + +/** + * An interface used by ImageReader implementations to + * notify callers of their image and thumbnail reading methods of + * progress. + * + *

This interface receives general indications of decoding + * progress (via the imageProgress and + * thumbnailProgress methods), and events indicating when + * an entire image has been updated (via the + * imageStarted, imageComplete, + * thumbnailStarted and thumbnailComplete + * methods). Applications that wish to be informed of pixel updates + * as they happen (for example, during progressive decoding), should + * provide an IIOReadUpdateListener. + * + * @see IIOReadUpdateListener + * @see javax.imageio.ImageReader#addIIOReadProgressListener + * @see javax.imageio.ImageReader#removeIIOReadProgressListener + * + */ +public interface IIOReadProgressListener extends EventListener { + + /** + * Reports that a sequence of read operations is beginning. + * ImageReader implementations are required to call + * this method exactly once from their + * readAll(Iterator) method. + * + * @param source the ImageReader object calling this method. + * @param minIndex the index of the first image to be read. + */ + void sequenceStarted(ImageReader source, int minIndex); + + /** + * Reports that a sequence of read operations has completed. + * ImageReader implementations are required to call + * this method exactly once from their + * readAll(Iterator) method. + * + * @param source the ImageReader object calling this method. + */ + void sequenceComplete(ImageReader source); + + /** + * Reports that an image read operation is beginning. All + * ImageReader implementations are required to call + * this method exactly once when beginning an image read + * operation. + * + * @param source the ImageReader object calling this method. + * @param imageIndex the index of the image being read within its + * containing input file or stream. + */ + void imageStarted(ImageReader source, int imageIndex); + + /** + * Reports the approximate degree of completion of the current + * read call of the associated + * ImageReader. + * + *

The degree of completion is expressed as a percentage + * varying from 0.0F to 100.0F. The + * percentage should ideally be calculated in terms of the + * remaining time to completion, but it is usually more practical + * to use a more well-defined metric such as pixels decoded or + * portion of input stream consumed. In any case, a sequence of + * calls to this method during a given read operation should + * supply a monotonically increasing sequence of percentage + * values. It is not necessary to supply the exact values + * 0 and 100, as these may be inferred + * by the callee from other methods. + * + *

Each particular ImageReader implementation may + * call this method at whatever frequency it desires. A rule of + * thumb is to call it around each 5 percent mark. + * + * @param source the ImageReader object calling this method. + * @param percentageDone the approximate percentage of decoding that + * has been completed. + */ + void imageProgress(ImageReader source, float percentageDone); + + /** + * Reports that the current image read operation has completed. + * All ImageReader implementations are required to + * call this method exactly once upon completion of each image + * read operation. + * + * @param source the ImageReader object calling this + * method. + */ + void imageComplete(ImageReader source); + + /** + * Reports that a thumbnail read operation is beginning. All + * ImageReader implementations are required to call + * this method exactly once when beginning a thumbnail read + * operation. + * + * @param source the ImageReader object calling this method. + * @param imageIndex the index of the image being read within its + * containing input file or stream. + * @param thumbnailIndex the index of the thumbnail being read. + */ + void thumbnailStarted(ImageReader source, + int imageIndex, int thumbnailIndex); + + /** + * Reports the approximate degree of completion of the current + * getThumbnail call within the associated + * ImageReader. The semantics are identical to those + * of imageProgress. + * + * @param source the ImageReader object calling this method. + * @param percentageDone the approximate percentage of decoding that + * has been completed. + */ + void thumbnailProgress(ImageReader source, float percentageDone); + + /** + * Reports that a thumbnail read operation has completed. All + * ImageReader implementations are required to call + * this method exactly once upon completion of each thumbnail read + * operation. + * + * @param source the ImageReader object calling this + * method. + */ + void thumbnailComplete(ImageReader source); + + /** + * Reports that a read has been aborted via the reader's + * abort method. No further notifications will be + * given. + * + * @param source the ImageReader object calling this + * method. + */ + void readAborted(ImageReader source); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadUpdateListener.java b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadUpdateListener.java new file mode 100644 index 000000000..48e03e624 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadUpdateListener.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.event; + +import java.awt.image.BufferedImage; +import java.util.EventListener; +import javax.imageio.ImageReader; + +/** + * An interface used by ImageReader implementations to + * notify callers of their image and thumbnail reading methods of + * pixel updates. + * + * @see javax.imageio.ImageReader#addIIOReadUpdateListener + * @see javax.imageio.ImageReader#removeIIOReadUpdateListener + * + */ +public interface IIOReadUpdateListener extends EventListener { + + /** + * Reports that the current read operation is about to begin a + * progressive pass. Readers of formats that support progressive + * encoding should use this to notify clients when each pass is + * completed when reading a progressively encoded image. + * + *

An estimate of the area that will be updated by the pass is + * indicated by the minX, minY, + * width, and height parameters. If the + * pass is interlaced, that is, it only updates selected rows or + * columns, the periodX and periodY + * parameters will indicate the degree of subsampling. The set of + * bands that may be affected is indicated by the value of + * bands. + * + * @param source the ImageReader object calling this + * method. + * @param theImage the BufferedImage being updated. + * @param pass the number of the pass that is about to begin, + * starting with 0. + * @param minPass the index of the first pass that will be decoded. + * @param maxPass the index of the last pass that will be decoded. + * @param minX the X coordinate of the leftmost updated column + * of pixels. + * @param minY the Y coordinate of the uppermost updated row + * of pixels. + * @param periodX the horizontal spacing between updated pixels; + * a value of 1 means no gaps. + * @param periodY the vertical spacing between updated pixels; + * a value of 1 means no gaps. + * @param bands an array of ints indicating the the + * set bands that may be updated. + */ + void passStarted(ImageReader source, + BufferedImage theImage, + int pass, + int minPass, int maxPass, + int minX, int minY, + int periodX, int periodY, + int[] bands); + + /** + * Reports that a given region of the image has been updated. + * The application might choose to redisplay the specified area, + * for example, in order to provide a progressive display effect, + * or perform other incremental processing. + * + *

Note that different image format readers may produce + * decoded pixels in a variety of different orders. Many readers + * will produce pixels in a simple top-to-bottom, + * left-to-right-order, but others may use multiple passes of + * interlacing, tiling, etc. The sequence of updates may even + * differ from call to call depending on network speeds, for + * example. A call to this method does not guarantee that all the + * specified pixels have actually been updated, only that some + * activity has taken place within some subregion of the one + * specified. + * + *

The particular ImageReader implementation may + * choose how often to provide updates. Each update specifies + * that a given region of the image has been updated since the + * last update. A region is described by its spatial bounding box + * (minX, minY, width, and + * height); X and Y subsampling factors + * (periodX and periodY); and a set of + * updated bands (bands). For example, the update: + * + *

+     * minX = 10
+     * minY = 20
+     * width = 3
+     * height = 4
+     * periodX = 2
+     * periodY = 3
+     * bands = { 1, 3 }
+     * 
+ * + * would indicate that bands 1 and 3 of the following pixels were + * updated: + * + *
+     * (10, 20) (12, 20) (14, 20)
+     * (10, 23) (12, 23) (14, 23)
+     * (10, 26) (12, 26) (14, 26)
+     * (10, 29) (12, 29) (14, 29)
+     * 
+ * + * @param source the ImageReader object calling this method. + * @param theImage the BufferedImage being updated. + * @param minX the X coordinate of the leftmost updated column + * of pixels. + * @param minY the Y coordinate of the uppermost updated row + * of pixels. + * @param width the number of updated pixels horizontally. + * @param height the number of updated pixels vertically. + * @param periodX the horizontal spacing between updated pixels; + * a value of 1 means no gaps. + * @param periodY the vertical spacing between updated pixels; + * a value of 1 means no gaps. + * @param bands an array of ints indicating which + * bands are being updated. + */ + void imageUpdate(ImageReader source, + BufferedImage theImage, + int minX, int minY, + int width, int height, + int periodX, int periodY, + int[] bands); + + /** + * Reports that the current read operation has completed a + * progressive pass. Readers of formats that support + * progressive encoding should use this to notify clients when + * each pass is completed when reading a progressively + * encoded image. + * + * @param source the ImageReader object calling this + * method. + * @param theImage the BufferedImage being updated. + * + * @see javax.imageio.ImageReadParam#setSourceProgressivePasses(int, int) + */ + void passComplete(ImageReader source, BufferedImage theImage); + + /** + * Reports that the current thumbnail read operation is about to + * begin a progressive pass. Readers of formats that support + * progressive encoding should use this to notify clients when + * each pass is completed when reading a progressively encoded + * thumbnail image. + * + * @param source the ImageReader object calling this + * method. + * @param theThumbnail the BufferedImage thumbnail + * being updated. + * @param pass the number of the pass that is about to begin, + * starting with 0. + * @param minPass the index of the first pass that will be decoded. + * @param maxPass the index of the last pass that will be decoded. + * @param minX the X coordinate of the leftmost updated column + * of pixels. + * @param minY the Y coordinate of the uppermost updated row + * of pixels. + * @param periodX the horizontal spacing between updated pixels; + * a value of 1 means no gaps. + * @param periodY the vertical spacing between updated pixels; + * a value of 1 means no gaps. + * @param bands an array of ints indicating the the + * set bands that may be updated. + * + * @see #passStarted + */ + void thumbnailPassStarted(ImageReader source, + BufferedImage theThumbnail, + int pass, + int minPass, int maxPass, + int minX, int minY, + int periodX, int periodY, + int[] bands); + + /** + * Reports that a given region of a thumbnail image has been updated. + * The application might choose to redisplay the specified area, + * for example, in order to provide a progressive display effect, + * or perform other incremental processing. + * + * @param source the ImageReader object calling this method. + * @param theThumbnail the BufferedImage thumbnail + * being updated. + * @param minX the X coordinate of the leftmost updated column + * of pixels. + * @param minY the Y coordinate of the uppermost updated row + * of pixels. + * @param width the number of updated pixels horizontally. + * @param height the number of updated pixels vertically. + * @param periodX the horizontal spacing between updated pixels; + * a value of 1 means no gaps. + * @param periodY the vertical spacing between updated pixels; + * a value of 1 means no gaps. + * @param bands an array of ints indicating which + * bands are being updated. + * + * @see #imageUpdate + */ + void thumbnailUpdate(ImageReader source, + BufferedImage theThumbnail, + int minX, int minY, + int width, int height, + int periodX, int periodY, + int[] bands); + + /** + * Reports that the current thumbnail read operation has completed + * a progressive pass. Readers of formats that support + * progressive encoding should use this to notify clients when + * each pass is completed when reading a progressively encoded + * thumbnail image. + * + * @param source the ImageReader object calling this + * method. + * @param theThumbnail the BufferedImage thumbnail + * being updated. + * + * @see #passComplete + */ + void thumbnailPassComplete(ImageReader source, BufferedImage theThumbnail); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadWarningListener.java b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadWarningListener.java new file mode 100644 index 000000000..3b2dd75ee --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOReadWarningListener.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.event; + +import java.util.EventListener; +import javax.imageio.ImageReader; + +/** + * An interface used by ImageReader implementations to + * notify callers of their image and thumbnail reading methods of + * warnings (non-fatal errors). Fatal errors cause the relevant + * read method to throw an IIOException. + * + *

Localization is handled by associating a Locale + * with each IIOReadWarningListener as it is registered + * with an ImageReader. It is up to the + * ImageReader to provide localized messages. + * + * @see javax.imageio.ImageReader#addIIOReadWarningListener + * @see javax.imageio.ImageReader#removeIIOReadWarningListener + * + */ +public interface IIOReadWarningListener extends EventListener { + + /** + * Reports the occurrence of a non-fatal error in decoding. Decoding + * will continue following the call to this method. The application + * may choose to display a dialog, print the warning to the console, + * ignore the warning, or take any other action it chooses. + * + * @param source the ImageReader object calling this method. + * @param warning a String containing the warning. + */ + void warningOccurred(ImageReader source, String warning); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOWriteProgressListener.java b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOWriteProgressListener.java new file mode 100644 index 000000000..24cc16a99 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOWriteProgressListener.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2000, 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.event; + +import java.util.EventListener; +import javax.imageio.ImageWriter; + +/** + * An interface used by ImageWriter implementations to notify + * callers of their image writing methods of progress. + * + * @see javax.imageio.ImageWriter#write + * + */ +public interface IIOWriteProgressListener extends EventListener { + + /** + * Reports that an image write operation is beginning. All + * ImageWriter implementations are required to call + * this method exactly once when beginning an image write + * operation. + * + * @param source the ImageWriter object calling this + * method. + * @param imageIndex the index of the image being written within + * its containing input file or stream. + */ + void imageStarted(ImageWriter source, int imageIndex); + + /** + * Reports the approximate degree of completion of the current + * write call within the associated + * ImageWriter. + * + *

The degree of completion is expressed as an index + * indicating which image is being written, and a percentage + * varying from 0.0F to 100.0F + * indicating how much of the current image has been output. The + * percentage should ideally be calculated in terms of the + * remaining time to completion, but it is usually more practical + * to use a more well-defined metric such as pixels decoded or + * portion of input stream consumed. In any case, a sequence of + * calls to this method during a given read operation should + * supply a monotonically increasing sequence of percentage + * values. It is not necessary to supply the exact values + * 0 and 100, as these may be inferred + * by the callee from other methods. + * + *

Each particular ImageWriter implementation may + * call this method at whatever frequency it desires. A rule of + * thumb is to call it around each 5 percent mark. + * + * @param source the ImageWriter object calling this method. + * @param percentageDone the approximate percentage of decoding that + * has been completed. + */ + void imageProgress(ImageWriter source, + float percentageDone); + + /** + * Reports that the image write operation has completed. All + * ImageWriter implementations are required to call + * this method exactly once upon completion of each image write + * operation. + * + * @param source the ImageWriter object calling this method. + */ + void imageComplete(ImageWriter source); + + /** + * Reports that a thumbnail write operation is beginning. All + * ImageWriter implementations are required to call + * this method exactly once when beginning a thumbnail write + * operation. + * + * @param source the ImageWrite object calling this method. + * @param imageIndex the index of the image being written within its + * containing input file or stream. + * @param thumbnailIndex the index of the thumbnail being written. + */ + void thumbnailStarted(ImageWriter source, + int imageIndex, int thumbnailIndex); + + /** + * Reports the approximate degree of completion of the current + * thumbnail write within the associated ImageWriter. + * The semantics are identical to those of + * imageProgress. + * + * @param source the ImageWriter object calling this + * method. + * @param percentageDone the approximate percentage of decoding that + * has been completed. + */ + void thumbnailProgress(ImageWriter source, float percentageDone); + + /** + * Reports that a thumbnail write operation has completed. All + * ImageWriter implementations are required to call + * this method exactly once upon completion of each thumbnail + * write operation. + * + * @param source the ImageWriter object calling this + * method. + */ + void thumbnailComplete(ImageWriter source); + + /** + * Reports that a write has been aborted via the writer's + * abort method. No further notifications will be + * given. + * + * @param source the ImageWriter object calling this + * method. + */ + void writeAborted(ImageWriter source); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOWriteWarningListener.java b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOWriteWarningListener.java new file mode 100644 index 000000000..67d4cf203 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/event/IIOWriteWarningListener.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.event; + +import java.util.EventListener; +import javax.imageio.ImageWriter; + +/** + * An interface used by ImageWriter implementations to + * notify callers of their image and thumbnail reading methods of + * warnings (non-fatal errors). Fatal errors cause the relevant + * read method to throw an IIOException. + * + *

Localization is handled by associating a Locale + * with each IIOWriteWarningListener as it is registered + * with an ImageWriter. It is up to the + * ImageWriter to provide localized messages. + * + * @see javax.imageio.ImageWriter#addIIOWriteWarningListener + * @see javax.imageio.ImageWriter#removeIIOWriteWarningListener + * + */ +public interface IIOWriteWarningListener extends EventListener { + + /** + * Reports the occurrence of a non-fatal error in encoding. Encoding + * will continue following the call to this method. The application + * may choose to display a dialog, print the warning to the console, + * ignore the warning, or take any other action it chooses. + * + * @param source the ImageWriter object calling this method. + * @param imageIndex the index, starting with 0, of the image + * generating the warning. + * @param warning a String containing the warning. + */ + void warningOccurred(ImageWriter source, + int imageIndex, + String warning); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/event/package.html b/sources/net.sf.j2s.java.core/src/javax/imageio/event/package.html new file mode 100644 index 000000000..000ab8bde --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/event/package.html @@ -0,0 +1,62 @@ + + + + + + + + + +A package of the Java Image I/O API dealing with synchronous +notification of events during the reading and writing of images. + +

+ +The IIOReadProgressListener interface allows for +notification of the percentage of an image that has been read +successfully. + +

+ +The IIOReadUpdateListener interface allows for +notification of the portions of an image that have been read. This is +useful, for example, for implementing dynamic display of an image as +it is loaded. + +

+ +The IIOReadWarningListener interface allows for +notification of non-fatal errors during reading. + +

+ +The IIOWriteWarningListener and +IIOWriteProgressListener interfaces perform analogous +functions for writers. + +@since 1.4 + + diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOInvalidTreeException.java b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOInvalidTreeException.java new file mode 100644 index 000000000..7f50cc80a --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOInvalidTreeException.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2000, 2001, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.metadata; + +import javax.imageio.IIOException; +import org.w3c.dom.Node; + +/** + * An IIOInvalidTreeException is thrown when an attempt + * by an IIOMetadata object to parse a tree of + * IIOMetadataNodes fails. The node that led to the + * parsing error may be stored. As with any parsing error, the actual + * error may occur at a different point that that where it is + * detected. The node returned by getOffendingNode + * should merely be considered as a clue to the actual nature of the + * problem. + * + * @see IIOMetadata#setFromTree + * @see IIOMetadata#mergeTree + * @see IIOMetadataNode + * + */ +public class IIOInvalidTreeException extends IIOException { + + /** + * The Node that led to the parsing error, or + * null. + */ + protected Node offendingNode = null; + + /** + * Constructs an IIOInvalidTreeException with a + * message string and a reference to the Node that + * caused the parsing error. + * + * @param message a String containing the reason for + * the parsing failure. + * @param offendingNode the DOM Node that caused the + * exception, or null. + */ + public IIOInvalidTreeException(String message, Node offendingNode) { + super(message); + this.offendingNode = offendingNode; + } + + /** + * Constructs an IIOInvalidTreeException with a + * message string, a reference to an exception that caused this + * exception, and a reference to the Node that caused + * the parsing error. + * + * @param message a String containing the reason for + * the parsing failure. + * @param cause the Throwable (Error or + * Exception) that caused this exception to occur, + * or null. + * @param offendingNode the DOM Node that caused the + * exception, or null. + */ + public IIOInvalidTreeException(String message, Throwable cause, + Node offendingNode) { + super(message, cause); + this.offendingNode = offendingNode; + } + + /** + * Returns the Node that caused the error in parsing. + * + * @return the offending Node. + */ + public Node getOffendingNode() { + return offendingNode; + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataController.java b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataController.java new file mode 100644 index 000000000..cf46dbcdf --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataController.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2000, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.metadata; + +/** + * An interface to be implemented by objects that can determine the + * settings of an IIOMetadata object, either by putting + * up a GUI to obtain values from a user, or by other means. This + * interface merely specifies a generic activate method + * that invokes the controller, without regard for how the controller + * obtains values (i.e., whether the controller puts up a GUI + * or merely computes a set of values is irrelevant to this + * interface). + * + *

Within the activate method, a controller obtains + * initial values by querying the IIOMetadata object's + * settings, either using the XML DOM tree or a plug-in specific + * interface, modifies values by whatever means, then modifies the + * IIOMetadata object's settings, using either the + * setFromTree or mergeTree methods, or a + * plug-in specific interface. In general, applications may expect + * that when the activate method returns + * true, the IIOMetadata object is ready for + * use in a write operation. + * + *

Vendors may choose to provide GUIs for the + * IIOMetadata subclasses they define for a particular + * plug-in. These can be set up as default controllers in the + * corresponding IIOMetadata subclasses. + * + *

Alternatively, an algorithmic process such as a database lookup + * or the parsing of a command line could be used as a controller, in + * which case the activate method would simply look up or + * compute the settings, call methods on IIOMetadata to + * set its state, and return true. + * + * @see IIOMetadata#setController + * @see IIOMetadata#getController + * @see IIOMetadata#getDefaultController + * @see IIOMetadata#hasController + * @see IIOMetadata#activateController + * + */ +public interface IIOMetadataController { + + /** + * Activates the controller. If true is returned, + * all settings in the IIOMetadata object should be + * ready for use in a write operation. If false is + * returned, no settings in the IIOMetadata object + * will be disturbed (i.e., the user canceled the + * operation). + * + * @param metadata the IIOMetadata object to be modified. + * + * @return true if the IIOMetadata has been + * modified, false otherwise. + * + * @exception IllegalArgumentException if metadata is + * null or is not an instance of the correct class. + */ + boolean activate(IIOMetadata metadata); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataFormat.java b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataFormat.java new file mode 100644 index 000000000..8c0f1336b --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataFormat.java @@ -0,0 +1,839 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.metadata; + +import java.util.Locale; +import javax.imageio.ImageTypeSpecifier; + +/** + * An object describing the structure of metadata documents returned + * from IIOMetadata.getAsTree and passed to + * IIOMetadata.setFromTree and mergeTree. + * Document structures are described by a set of constraints on the + * type and number of child elements that may belong to a given parent + * element type, the names, types, and values of attributes that may + * belong to an element, and the type and values of + * Object reference that may be stored at a node. + * + *

N.B: classes that implement this interface should contain a + * method declared as public static getInstance() which + * returns an instance of the class. Commonly, an implementation will + * construct only a single instance and cache it for future + * invocations of getInstance. + * + *

The structures that may be described by this class are a subset + * of those expressible using XML document type definitions (DTDs), + * with the addition of some basic information on the datatypes of + * attributes and the ability to store an Object + * reference within a node. In the future, XML Schemas could be used + * to represent these structures, and many others. + * + *

The differences between + * IIOMetadataFormat-described structures and DTDs are as + * follows: + * + *

    + *
  • Elements may not contain text or mix text with embedded + * tags. + * + *
  • The children of an element must conform to one of a few simple + * patterns, described in the documentation for the + * CHILD_* constants; + * + *
  • The in-memory representation of an elements may contain a + * reference to an Object. There is no provision for + * representing such objects textually. + *
+ * + */ +public interface IIOMetadataFormat { + + // Child policies + + /** + * A constant returned by getChildPolicy to indicate + * that an element may not have any children. In other words, it + * is required to be a leaf node. + */ + int CHILD_POLICY_EMPTY = 0; + + /** + * A constant returned by getChildPolicy to indicate + * that an element must have a single instance of each of its + * legal child elements, in order. In DTD terms, the contents of + * the element are defined by a sequence a,b,c,d,.... + */ + int CHILD_POLICY_ALL = 1; + + /** + * A constant returned by getChildPolicy to indicate + * that an element must have zero or one instance of each of its + * legal child elements, in order. In DTD terms, the contents of + * the element are defined by a sequence + * a?,b?,c?,d?,.... + */ + int CHILD_POLICY_SOME = 2; + + /** + * A constant returned by getChildPolicy to indicate + * that an element must have zero or one children, selected from + * among its legal child elements. In DTD terms, the contents of + * the element are defined by a selection + * a|b|c|d|.... + */ + int CHILD_POLICY_CHOICE = 3; + + /** + * A constant returned by getChildPolicy to indicate + * that an element must have a sequence of instances of any of its + * legal child elements. In DTD terms, the contents of the + * element are defined by a sequence (a|b|c|d|...)*. + */ + int CHILD_POLICY_SEQUENCE = 4; + + /** + * A constant returned by getChildPolicy to indicate + * that an element must have zero or more instances of its unique + * legal child element. In DTD terms, the contents of the element + * are defined by a starred expression a*. + */ + int CHILD_POLICY_REPEAT = 5; + + /** + * The largest valid CHILD_POLICY_* constant, + * to be used for range checks. + */ + int CHILD_POLICY_MAX = CHILD_POLICY_REPEAT; + + /** + * A constant returned by getObjectValueType to + * indicate the absence of a user object. + */ + int VALUE_NONE = 0; + + /** + * A constant returned by getAttributeValueType and + * getObjectValueType to indicate that the attribute + * or user object may be set a single, arbitrary value. + */ + int VALUE_ARBITRARY = 1; + + /** + * A constant returned by getAttributeValueType and + * getObjectValueType to indicate that the attribute + * or user object may be set a range of values. Both the minimum + * and maximum values of the range are exclusive. It is + * recommended that ranges of integers be inclusive on both ends, + * and that exclusive ranges be used only for floating-point data. + * + * @see #VALUE_RANGE_MIN_MAX_INCLUSIVE + */ + int VALUE_RANGE = 2; + + /** + * A value that may be or'ed with VALUE_RANGE to + * obtain VALUE_RANGE_MIN_INCLUSIVE, and with + * VALUE_RANGE_MAX_INCLUSIVE to obtain + * VALUE_RANGE_MIN_MAX_INCLUSIVE. + * + *

Similarly, the value may be and'ed with the value of + * getAttributeValueTypeor + * getObjectValueType to determine if the minimum + * value of the range is inclusive. + */ + int VALUE_RANGE_MIN_INCLUSIVE_MASK = 4; + + /** + * A value that may be or'ed with VALUE_RANGE to + * obtain VALUE_RANGE_MAX_INCLUSIVE, and with + * VALUE_RANGE_MIN_INCLUSIVE to obtain + * VALUE_RANGE_MIN_MAX_INCLUSIVE. + * + *

Similarly, the value may be and'ed with the value of + * getAttributeValueTypeor + * getObjectValueType to determine if the maximum + * value of the range is inclusive. + */ + int VALUE_RANGE_MAX_INCLUSIVE_MASK = 8; + + /** + * A constant returned by getAttributeValueType and + * getObjectValueType to indicate that the attribute + * or user object may be set to a range of values. The minimum + * (but not the maximum) value of the range is inclusive. + */ + int VALUE_RANGE_MIN_INCLUSIVE = VALUE_RANGE | + VALUE_RANGE_MIN_INCLUSIVE_MASK; + + /** + * A constant returned by getAttributeValueType and + * getObjectValueType to indicate that the attribute + * or user object may be set to a range of values. The maximum + * (but not the minimum) value of the range is inclusive. + */ + int VALUE_RANGE_MAX_INCLUSIVE = VALUE_RANGE | + VALUE_RANGE_MAX_INCLUSIVE_MASK; + + /** + * A constant returned by getAttributeValueType and + * getObjectValueType to indicate that the attribute + * or user object may be set a range of values. Both the minimum + * and maximum values of the range are inclusive. It is + * recommended that ranges of integers be inclusive on both ends, + * and that exclusive ranges be used only for floating-point data. + */ + int VALUE_RANGE_MIN_MAX_INCLUSIVE = + VALUE_RANGE | + VALUE_RANGE_MIN_INCLUSIVE_MASK | + VALUE_RANGE_MAX_INCLUSIVE_MASK; + + /** + * A constant returned by getAttributeValueType and + * getObjectValueType to indicate that the attribute + * or user object may be set one of a number of enumerated values. + * In the case of attributes, these values are + * Strings; for objects, they are + * Objects implementing a given class or interface. + * + *

Attribute values of type DATATYPE_BOOLEAN + * should be marked as enumerations. + */ + int VALUE_ENUMERATION = 16; + + /** + * A constant returned by getAttributeValueType and + * getObjectValueType to indicate that the attribute + * or user object may be set to a list or array of values. In the + * case of attributes, the list will consist of + * whitespace-separated values within a String; for + * objects, an array will be used. + */ + int VALUE_LIST = 32; + + /** + * A constant returned by getAttributeDataType + * indicating that the value of an attribute is a general Unicode + * string. + */ + int DATATYPE_STRING = 0; + + /** + * A constant returned by getAttributeDataType + * indicating that the value of an attribute is one of the boolean + * values 'true' or 'false'. + * Attribute values of type DATATYPE_BOOLEAN should be marked as + * enumerations, and the permitted values should be the string + * literal values "TRUE" or "FALSE", although a plugin may also + * recognise lower or mixed case equivalents. + */ + int DATATYPE_BOOLEAN = 1; + + /** + * A constant returned by getAttributeDataType + * indicating that the value of an attribute is a string + * representation of an integer. + */ + int DATATYPE_INTEGER = 2; + + /** + * A constant returned by getAttributeDataType + * indicating that the value of an attribute is a string + * representation of a decimal floating-point number. + */ + int DATATYPE_FLOAT = 3; + + /** + * A constant returned by getAttributeDataType + * indicating that the value of an attribute is a string + * representation of a double-precision decimal floating-point + * number. + */ + int DATATYPE_DOUBLE = 4; + + // Root + + /** + * Returns the name of the root element of the format. + * + * @return a String. + */ + String getRootName(); + + // Multiplicity + + /** + * Returns true if the element (and the subtree below + * it) is allowed to appear in a metadata document for an image of + * the given type, defined by an ImageTypeSpecifier. + * For example, a metadata document format might contain an + * element that describes the primary colors of the image, which + * would not be allowed when writing a grayscale image. + * + * @param elementName the name of the element being queried. + * @param imageType an ImageTypeSpecifier indicating + * the type of the image that will be associated with the + * metadata. + * + * @return true if the node is meaningful for images + * of the given type. + */ + boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType); + + /** + * Returns the minimum number of children of the named element + * with child policy CHILD_POLICY_REPEAT. For + * example, an element representing color primary information + * might be required to have at least 3 children, one for each + * primary. + * + * @param elementName the name of the element being queried. + * + * @return an int. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element does + * not have a child policy of CHILD_POLICY_REPEAT. + */ + int getElementMinChildren(String elementName); + + /** + * Returns the maximum number of children of the named element + * with child policy CHILD_POLICY_REPEAT. For + * example, an element representing an entry in an 8-bit color + * palette might be allowed to repeat up to 256 times. A value of + * Integer.MAX_VALUE may be used to specify that + * there is no upper bound. + * + * @param elementName the name of the element being queried. + * + * @return an int. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element does + * not have a child policy of CHILD_POLICY_REPEAT. + */ + int getElementMaxChildren(String elementName); + + /** + * Returns a String containing a description of the + * named element, or null. The description will be + * localized for the supplied Locale if possible. + * + *

If locale is null, the current + * default Locale returned by Locale.getLocale + * will be used. + * + * @param elementName the name of the element. + * @param locale the Locale for which localization + * will be attempted. + * + * @return the element description. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this format. + */ + String getElementDescription(String elementName, Locale locale); + + // Children + + /** + * Returns one of the constants starting with + * CHILD_POLICY_, indicating the legal pattern of + * children for the named element. + * + * @param elementName the name of the element being queried. + * + * @return one of the CHILD_POLICY_* constants. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + */ + int getChildPolicy(String elementName); + + /** + * Returns an array of Strings indicating the names + * of the element which are allowed to be children of the named + * element, in the order in which they should appear. If the + * element cannot have children, null is returned. + * + * @param elementName the name of the element being queried. + * + * @return an array of Strings, or null. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + */ + String[] getChildNames(String elementName); + + // Attributes + + /** + * Returns an array of Strings listing the names of + * the attributes that may be associated with the named element. + * + * @param elementName the name of the element being queried. + * + * @return an array of Strings. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + */ + String[] getAttributeNames(String elementName); + + /** + * Returns one of the constants starting with VALUE_, + * indicating whether the values of the given attribute within the + * named element are arbitrary, constrained to lie within a + * specified range, constrained to be one of a set of enumerated + * values, or are a whitespace-separated list of arbitrary values. + * + * @param elementName the name of the element being queried. + * @param attrName the name of the attribute being queried. + * + * @return one of the VALUE_* constants. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + */ + int getAttributeValueType(String elementName, String attrName); + + /** + * Returns one of the constants starting with + * DATATYPE_, indicating the format and + * interpretation of the value of the given attribute within the + * named element. If getAttributeValueType returns + * VALUE_LIST, then the legal value is a + * whitespace-spearated list of values of the returned datatype. + * + * @param elementName the name of the element being queried. + * @param attrName the name of the attribute being queried. + * + * @return one of the DATATYPE_* constants. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + */ + int getAttributeDataType(String elementName, String attrName); + + /** + * Returns true if the named attribute must be + * present within the named element. + * + * @param elementName the name of the element being queried. + * @param attrName the name of the attribute being queried. + * + * @return true if the attribute must be present. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + */ + boolean isAttributeRequired(String elementName, String attrName); + + /** + * Returns the default value of the named attribute, if it is not + * explicitly present within the named element, as a + * String, or null if no default value + * is available. + * + * @param elementName the name of the element being queried. + * @param attrName the name of the attribute being queried. + * + * @return a String containing the default value, or + * null. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + */ + String getAttributeDefaultValue(String elementName, String attrName); + + /** + * Returns an array of Strings containing the legal + * enumerated values for the given attribute within the named + * element. This method should only be called if + * getAttributeValueType returns + * VALUE_ENUMERATION. + * + * @param elementName the name of the element being queried. + * @param attrName the name of the attribute being queried. + * + * @return an array of Strings. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + * @exception IllegalArgumentException if the given attribute is + * not defined as an enumeration. + */ + String[] getAttributeEnumerations(String elementName, String attrName); + + /** + * Returns the minimum legal value for the attribute. Whether + * this value is inclusive or exclusive may be determined by the + * value of getAttributeValueType. The value is + * returned as a String; its interpretation is + * dependent on the value of getAttributeDataType. + * This method should only be called if + * getAttributeValueType returns + * VALUE_RANGE_*. + * + * @param elementName the name of the element being queried. + * @param attrName the name of the attribute being queried. + * + * @return a String containing the smallest legal + * value for the attribute. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + * @exception IllegalArgumentException if the given attribute is + * not defined as a range. + */ + String getAttributeMinValue(String elementName, String attrName); + + /** + * Returns the maximum legal value for the attribute. Whether + * this value is inclusive or exclusive may be determined by the + * value of getAttributeValueType. The value is + * returned as a String; its interpretation is + * dependent on the value of getAttributeDataType. + * This method should only be called if + * getAttributeValueType returns + * VALUE_RANGE_*. + * + * @param elementName the name of the element being queried, as a + * String. + * @param attrName the name of the attribute being queried. + * + * @return a String containing the largest legal + * value for the attribute. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + * @exception IllegalArgumentException if the given attribute is + * not defined as a range. + */ + String getAttributeMaxValue(String elementName, String attrName); + + /** + * Returns the minimum number of list items that may be used to + * define this attribute. The attribute itself is defined as a + * String containing multiple whitespace-separated + * items. This method should only be called if + * getAttributeValueType returns + * VALUE_LIST. + * + * @param elementName the name of the element being queried. + * @param attrName the name of the attribute being queried. + * + * @return the smallest legal number of list items for the + * attribute. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + * @exception IllegalArgumentException if the given attribute is + * not defined as a list. + */ + int getAttributeListMinLength(String elementName, String attrName); + + /** + * Returns the maximum number of list items that may be used to + * define this attribute. A value of + * Integer.MAX_VALUE may be used to specify that + * there is no upper bound. The attribute itself is defined as a + * String containing multiple whitespace-separated + * items. This method should only be called if + * getAttributeValueType returns + * VALUE_LIST. + * + * @param elementName the name of the element being queried. + * @param attrName the name of the attribute being queried. + * + * @return the largest legal number of list items for the + * attribute. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + * @exception IllegalArgumentException if the given attribute is + * not defined as a list. + */ + int getAttributeListMaxLength(String elementName, String attrName); + + /** + * Returns a String containing a description of the + * named attribute, or null. The description will be + * localized for the supplied Locale if possible. + * + *

If locale is null, the current + * default Locale returned by Locale.getLocale + * will be used. + * + * @param elementName the name of the element. + * @param attrName the name of the attribute. + * @param locale the Locale for which localization + * will be attempted. + * + * @return the attribute description. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + */ + String getAttributeDescription(String elementName, String attrName, + Locale locale); + + // Object value + + /** + * Returns one of the enumerated values starting with + * VALUE_, indicating the type of values + * (enumeration, range, or array) that are allowed for the + * Object reference. If no object value can be + * stored within the given element, the result of this method will + * be VALUE_NONE. + * + *

Object references whose legal values are + * defined as a range must implement the Comparable + * interface. + * + * @param elementName the name of the element being queried. + * + * @return one of the VALUE_* constants. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * + * @see Comparable + */ + int getObjectValueType(String elementName); + + /** + * Returns the Class type of the Object + * reference stored within the element. If this element may not + * contain an Object reference, an + * IllegalArgumentException will be thrown. If the + * class type is an array, this field indicates the underlying + * class type (e.g, for an array of ints, this + * method would return int.class). + * + *

Object references whose legal values are + * defined as a range must implement the Comparable + * interface. + * + * @param elementName the name of the element being queried. + * + * @return a Class object. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element cannot + * contain an object value (i.e., if + * getObjectValueType(elementName) == VALUE_NONE). + */ + Class getObjectClass(String elementName); + + /** + * Returns an Objects containing the default + * value for the Object reference within + * the named element. + * + * @param elementName the name of the element being queried. + * + * @return an Object. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element cannot + * contain an object value (i.e., if + * getObjectValueType(elementName) == VALUE_NONE). + */ + Object getObjectDefaultValue(String elementName); + + /** + * Returns an array of Objects containing the legal + * enumerated values for the Object reference within + * the named element. This method should only be called if + * getObjectValueType returns + * VALUE_ENUMERATION. + * + *

The Object associated with a node that accepts + * enumerated values must be equal to one of the values returned by + * this method, as defined by the == operator (as + * opposed to the Object.equals method). + * + * @param elementName the name of the element being queried. + * + * @return an array of Objects. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element cannot + * contain an object value (i.e., if + * getObjectValueType(elementName) == VALUE_NONE). + * @exception IllegalArgumentException if the Object + * is not defined as an enumeration. + */ + Object[] getObjectEnumerations(String elementName); + + /** + * Returns the minimum legal value for the Object + * reference within the named element. Whether this value is + * inclusive or exclusive may be determined by the value of + * getObjectValueType. This method should only be + * called if getObjectValueType returns one of the + * constants starting with VALUE_RANGE. + * + * @param elementName the name of the element being queried. + * + * @return the smallest legal value for the attribute. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element cannot + * contain an object value (i.e., if + * getObjectValueType(elementName) == VALUE_NONE). + * @exception IllegalArgumentException if the Object + * is not defined as a range. + */ + Comparable getObjectMinValue(String elementName); + + /** + * Returns the maximum legal value for the Object + * reference within the named element. Whether this value is + * inclusive or exclusive may be determined by the value of + * getObjectValueType. This method should only be + * called if getObjectValueType returns one of the + * constants starting with VALUE_RANGE. + * + * @return the smallest legal value for the attribute. + * + * @param elementName the name of the element being queried. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element cannot + * contain an object value (i.e., if + * getObjectValueType(elementName) == VALUE_NONE). + * @exception IllegalArgumentException if the Object + * is not defined as a range. + */ + Comparable getObjectMaxValue(String elementName); + + /** + * Returns the minimum number of array elements that may be used + * to define the Object reference within the named + * element. This method should only be called if + * getObjectValueType returns + * VALUE_LIST. + * + * @param elementName the name of the element being queried. + * + * @return the smallest valid array length for the + * Object reference. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element cannot + * contain an object value (i.e., if + * getObjectValueType(elementName) == VALUE_NONE). + * @exception IllegalArgumentException if the Object is not + * an array. + */ + int getObjectArrayMinLength(String elementName); + + /** + * Returns the maximum number of array elements that may be used + * to define the Object reference within the named + * element. A value of Integer.MAX_VALUE may be used + * to specify that there is no upper bound. This method should + * only be called if getObjectValueType returns + * VALUE_LIST. + * + * @param elementName the name of the element being queried. + * + * @return the largest valid array length for the + * Object reference. + * + * @exception IllegalArgumentException if elementName + * is null or is not a legal element name for this + * format. + * @exception IllegalArgumentException if the named element cannot + * contain an object value (i.e., if + * getObjectValueType(elementName) == VALUE_NONE). + * @exception IllegalArgumentException if the Object is not + * an array. + */ + int getObjectArrayMaxLength(String elementName); +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataFormatImpl.java b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataFormatImpl.java new file mode 100644 index 000000000..8504476f5 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataFormatImpl.java @@ -0,0 +1,1273 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.metadata; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import javax.imageio.ImageTypeSpecifier; +import com.sun.imageio.plugins.common.StandardMetadataFormat; + +/** + * A concrete class providing a reusable implementation of the + * IIOMetadataFormat interface. In addition, a static + * instance representing the standard, plug-in neutral + * javax_imageio_1.0 format is provided by the + * getStandardFormatInstance method. + * + *

In order to supply localized descriptions of elements and + * attributes, a ResourceBundle with a base name of + * this.getClass().getName() + "Resources" should be + * supplied via the usual mechanism used by + * ResourceBundle.getBundle. Briefly, the subclasser + * supplies one or more additional classes according to a naming + * convention (by default, the fully-qualified name of the subclass + * extending IIMetadataFormatImpl, plus the string + * "Resources", plus the country, language, and variant codes + * separated by underscores). At run time, calls to + * getElementDescription or + * getAttributeDescription will attempt to load such + * classes dynamically according to the supplied locale, and will use + * either the element name, or the element name followed by a '/' + * character followed by the attribute name as a key. This key will + * be supplied to the ResourceBundle's + * getString method, and the resulting localized + * description of the node or attribute is returned. + * + *

The subclass may supply a different base name for the resource + * bundles using the setResourceBaseName method. + * + *

A subclass may choose its own localization mechanism, if so + * desired, by overriding the supplied implementations of + * getElementDescription and + * getAttributeDescription. + * + * @see ResourceBundle#getBundle(String,Locale) + * + */ +public abstract class IIOMetadataFormatImpl implements IIOMetadataFormat { + + /** + * A String constant containing the standard format + * name, "javax_imageio_1.0". + */ + public static final String standardMetadataFormatName = + "javax_imageio_1.0"; + + private static IIOMetadataFormat standardFormat = null; + + private String resourceBaseName = this.getClass().getName() + "Resources"; + + private String rootName; + + // Element name (String) -> Element + private HashMap elementMap = new HashMap(); + + class Element { + String elementName; + + int childPolicy; + int minChildren = 0; + int maxChildren = 0; + + // Child names (Strings) + List childList = new ArrayList(); + + // Parent names (Strings) + List parentList = new ArrayList(); + + // List of attribute names in the order they were added + List attrList = new ArrayList(); + // Attr name (String) -> Attribute + Map attrMap = new HashMap(); + + ObjectValue objectValue; + } + + class Attribute { + String attrName; + + int valueType = VALUE_ARBITRARY; + int dataType; + boolean required; + String defaultValue = null; + + // enumeration + List enumeratedValues; + + // range + String minValue; + String maxValue; + + // list + int listMinLength; + int listMaxLength; + } + + class ObjectValue { + int valueType = VALUE_NONE; + Class classType = null; + Object defaultValue = null; + + // Meaningful only if valueType == VALUE_ENUMERATION + List enumeratedValues = null; + + // Meaningful only if valueType == VALUE_RANGE + Comparable minValue = null; + Comparable maxValue = null; + + // Meaningful only if valueType == VALUE_LIST + int arrayMinLength = 0; + int arrayMaxLength = 0; + } + + /** + * Constructs a blank IIOMetadataFormatImpl instance, + * with a given root element name and child policy (other than + * CHILD_POLICY_REPEAT). Additional elements, and + * their attributes and Object reference information + * may be added using the various add methods. + * + * @param rootName the name of the root element. + * @param childPolicy one of the CHILD_POLICY_* constants, + * other than CHILD_POLICY_REPEAT. + * + * @exception IllegalArgumentException if rootName is + * null. + * @exception IllegalArgumentException if childPolicy is + * not one of the predefined constants. + */ + public IIOMetadataFormatImpl(String rootName, + int childPolicy) { + if (rootName == null) { + throw new IllegalArgumentException("rootName == null!"); + } + if (childPolicy < CHILD_POLICY_EMPTY || + childPolicy > CHILD_POLICY_MAX || + childPolicy == CHILD_POLICY_REPEAT) { + throw new IllegalArgumentException("Invalid value for childPolicy!"); + } + + this.rootName = rootName; + + Element root = new Element(); + root.elementName = rootName; + root.childPolicy = childPolicy; + + elementMap.put(rootName, root); + } + + /** + * Constructs a blank IIOMetadataFormatImpl instance, + * with a given root element name and a child policy of + * CHILD_POLICY_REPEAT. Additional elements, and + * their attributes and Object reference information + * may be added using the various add methods. + * + * @param rootName the name of the root element. + * @param minChildren the minimum number of children of the node. + * @param maxChildren the maximum number of children of the node. + * + * @exception IllegalArgumentException if rootName is + * null. + * @exception IllegalArgumentException if minChildren + * is negative or larger than maxChildren. + */ + public IIOMetadataFormatImpl(String rootName, + int minChildren, + int maxChildren) { + if (rootName == null) { + throw new IllegalArgumentException("rootName == null!"); + } + if (minChildren < 0) { + throw new IllegalArgumentException("minChildren < 0!"); + } + if (minChildren > maxChildren) { + throw new IllegalArgumentException("minChildren > maxChildren!"); + } + + Element root = new Element(); + root.elementName = rootName; + root.childPolicy = CHILD_POLICY_REPEAT; + root.minChildren = minChildren; + root.maxChildren = maxChildren; + + this.rootName = rootName; + elementMap.put(rootName, root); + } + + /** + * Sets a new base name for locating ResourceBundles + * containing descriptions of elements and attributes for this + * format. + * + *

Prior to the first time this method is called, the base + * name will be equal to this.getClass().getName() + + * "Resources". + * + * @param resourceBaseName a String containing the new + * base name. + * + * @exception IllegalArgumentException if + * resourceBaseName is null. + * + * @see #getResourceBaseName + */ + protected void setResourceBaseName(String resourceBaseName) { + if (resourceBaseName == null) { + throw new IllegalArgumentException("resourceBaseName == null!"); + } + this.resourceBaseName = resourceBaseName; + } + + /** + * Returns the currently set base name for locating + * ResourceBundles. + * + * @return a String containing the base name. + * + * @see #setResourceBaseName + */ + protected String getResourceBaseName() { + return resourceBaseName; + } + + /** + * Utility method for locating an element. + * + * @param mustAppear if true, throw an + * IllegalArgumentException if no such node exists; + * if false, just return null. + */ + private Element getElement(String elementName, boolean mustAppear) { + if (mustAppear && (elementName == null)) { + throw new IllegalArgumentException("element name is null!"); + } + Element element = (Element)elementMap.get(elementName); + if (mustAppear && (element == null)) { + throw new IllegalArgumentException("No such element: " + + elementName); + } + return element; + } + + private Element getElement(String elementName) { + return getElement(elementName, true); + } + + // Utility method for locating an attribute + private Attribute getAttribute(String elementName, String attrName) { + Element element = getElement(elementName); + Attribute attr = (Attribute)element.attrMap.get(attrName); + if (attr == null) { + throw new IllegalArgumentException("No such attribute \"" + + attrName + "\"!"); + } + return attr; + } + + // Setup + + /** + * Adds a new element type to this metadata document format with a + * child policy other than CHILD_POLICY_REPEAT. + * + * @param elementName the name of the new element. + * @param parentName the name of the element that will be the + * parent of the new element. + * @param childPolicy one of the CHILD_POLICY_* + * constants, other than CHILD_POLICY_REPEAT, + * indicating the child policy of the new element. + * + * @exception IllegalArgumentException if parentName + * is null, or is not a legal element name for this + * format. + * @exception IllegalArgumentException if childPolicy + * is not one of the predefined constants. + */ + protected void addElement(String elementName, + String parentName, + int childPolicy) { + Element parent = getElement(parentName); + if (childPolicy < CHILD_POLICY_EMPTY || + childPolicy > CHILD_POLICY_MAX || + childPolicy == CHILD_POLICY_REPEAT) { + throw new IllegalArgumentException + ("Invalid value for childPolicy!"); + } + + Element element = new Element(); + element.elementName = elementName; + element.childPolicy = childPolicy; + + parent.childList.add(elementName); + element.parentList.add(parentName); + + elementMap.put(elementName, element); + } + + /** + * Adds a new element type to this metadata document format with a + * child policy of CHILD_POLICY_REPEAT. + * + * @param elementName the name of the new element. + * @param parentName the name of the element that will be the + * parent of the new element. + * @param minChildren the minimum number of children of the node. + * @param maxChildren the maximum number of children of the node. + * + * @exception IllegalArgumentException if parentName + * is null, or is not a legal element name for this + * format. + * @exception IllegalArgumentException if minChildren + * is negative or larger than maxChildren. + */ + protected void addElement(String elementName, + String parentName, + int minChildren, + int maxChildren) { + Element parent = getElement(parentName); + if (minChildren < 0) { + throw new IllegalArgumentException("minChildren < 0!"); + } + if (minChildren > maxChildren) { + throw new IllegalArgumentException("minChildren > maxChildren!"); + } + + Element element = new Element(); + element.elementName = elementName; + element.childPolicy = CHILD_POLICY_REPEAT; + element.minChildren = minChildren; + element.maxChildren = maxChildren; + + parent.childList.add(elementName); + element.parentList.add(parentName); + + elementMap.put(elementName, element); + } + + /** + * Adds an existing element to the list of legal children for a + * given parent node type. + * + * @param parentName the name of the element that will be the + * new parent of the element. + * @param elementName the name of the element to be added as a + * child. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this + * format. + * @exception IllegalArgumentException if parentName + * is null, or is not a legal element name for this + * format. + */ + protected void addChildElement(String elementName, String parentName) { + Element parent = getElement(parentName); + Element element = getElement(elementName); + parent.childList.add(elementName); + element.parentList.add(parentName); + } + + /** + * Removes an element from the format. If no element with the + * given name was present, nothing happens and no exception is + * thrown. + * + * @param elementName the name of the element to be removed. + */ + protected void removeElement(String elementName) { + Element element = getElement(elementName, false); + if (element != null) { + Iterator iter = element.parentList.iterator(); + while (iter.hasNext()) { + String parentName = (String)iter.next(); + Element parent = getElement(parentName, false); + if (parent != null) { + parent.childList.remove(elementName); + } + } + elementMap.remove(elementName); + } + } + + /** + * Adds a new attribute to a previously defined element that may + * be set to an arbitrary value. + * + * @param elementName the name of the element. + * @param attrName the name of the attribute being added. + * @param dataType the data type (string format) of the attribute, + * one of the DATATYPE_* constants. + * @param required true if the attribute must be present. + * @param defaultValue the default value for the attribute, or + * null. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null. + * @exception IllegalArgumentException if dataType is + * not one of the predefined constants. + */ + protected void addAttribute(String elementName, + String attrName, + int dataType, + boolean required, + String defaultValue) { + Element element = getElement(elementName); + if (attrName == null) { + throw new IllegalArgumentException("attrName == null!"); + } + if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) { + throw new IllegalArgumentException("Invalid value for dataType!"); + } + + Attribute attr = new Attribute(); + attr.attrName = attrName; + attr.valueType = VALUE_ARBITRARY; + attr.dataType = dataType; + attr.required = required; + attr.defaultValue = defaultValue; + + element.attrList.add(attrName); + element.attrMap.put(attrName, attr); + } + + /** + * Adds a new attribute to a previously defined element that will + * be defined by a set of enumerated values. + * + * @param elementName the name of the element. + * @param attrName the name of the attribute being added. + * @param dataType the data type (string format) of the attribute, + * one of the DATATYPE_* constants. + * @param required true if the attribute must be present. + * @param defaultValue the default value for the attribute, or + * null. + * @param enumeratedValues a List of + * Strings containing the legal values for the + * attribute. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null. + * @exception IllegalArgumentException if dataType is + * not one of the predefined constants. + * @exception IllegalArgumentException if + * enumeratedValues is null. + * @exception IllegalArgumentException if + * enumeratedValues does not contain at least one + * entry. + * @exception IllegalArgumentException if + * enumeratedValues contains an element that is not a + * String or is null. + */ + protected void addAttribute(String elementName, + String attrName, + int dataType, + boolean required, + String defaultValue, + List enumeratedValues) { + Element element = getElement(elementName); + if (attrName == null) { + throw new IllegalArgumentException("attrName == null!"); + } + if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) { + throw new IllegalArgumentException("Invalid value for dataType!"); + } + if (enumeratedValues == null) { + throw new IllegalArgumentException("enumeratedValues == null!"); + } + if (enumeratedValues.size() == 0) { + throw new IllegalArgumentException("enumeratedValues is empty!"); + } + Iterator iter = enumeratedValues.iterator(); + while (iter.hasNext()) { + Object o = iter.next(); + if (o == null) { + throw new IllegalArgumentException + ("enumeratedValues contains a null!"); + } + if (!(o instanceof String)) { + throw new IllegalArgumentException + ("enumeratedValues contains a non-String value!"); + } + } + + Attribute attr = new Attribute(); + attr.attrName = attrName; + attr.valueType = VALUE_ENUMERATION; + attr.dataType = dataType; + attr.required = required; + attr.defaultValue = defaultValue; + attr.enumeratedValues = enumeratedValues; + + element.attrList.add(attrName); + element.attrMap.put(attrName, attr); + } + + /** + * Adds a new attribute to a previously defined element that will + * be defined by a range of values. + * + * @param elementName the name of the element. + * @param attrName the name of the attribute being added. + * @param dataType the data type (string format) of the attribute, + * one of the DATATYPE_* constants. + * @param required true if the attribute must be present. + * @param defaultValue the default value for the attribute, or + * null. + * @param minValue the smallest (inclusive or exclusive depending + * on the value of minInclusive) legal value for the + * attribute, as a String. + * @param maxValue the largest (inclusive or exclusive depending + * on the value of minInclusive) legal value for the + * attribute, as a String. + * @param minInclusive true if minValue + * is inclusive. + * @param maxInclusive true if maxValue + * is inclusive. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null. + * @exception IllegalArgumentException if dataType is + * not one of the predefined constants. + */ + protected void addAttribute(String elementName, + String attrName, + int dataType, + boolean required, + String defaultValue, + String minValue, + String maxValue, + boolean minInclusive, + boolean maxInclusive) { + Element element = getElement(elementName); + if (attrName == null) { + throw new IllegalArgumentException("attrName == null!"); + } + if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) { + throw new IllegalArgumentException("Invalid value for dataType!"); + } + + Attribute attr = new Attribute(); + attr.attrName = attrName; + attr.valueType = VALUE_RANGE; + if (minInclusive) { + attr.valueType |= VALUE_RANGE_MIN_INCLUSIVE_MASK; + } + if (maxInclusive) { + attr.valueType |= VALUE_RANGE_MAX_INCLUSIVE_MASK; + } + attr.dataType = dataType; + attr.required = required; + attr.defaultValue = defaultValue; + attr.minValue = minValue; + attr.maxValue = maxValue; + + element.attrList.add(attrName); + element.attrMap.put(attrName, attr); + } + + /** + * Adds a new attribute to a previously defined element that will + * be defined by a list of values. + * + * @param elementName the name of the element. + * @param attrName the name of the attribute being added. + * @param dataType the data type (string format) of the attribute, + * one of the DATATYPE_* constants. + * @param required true if the attribute must be present. + * @param listMinLength the smallest legal number of list items. + * @param listMaxLength the largest legal number of list items. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null. + * @exception IllegalArgumentException if dataType is + * not one of the predefined constants. + * @exception IllegalArgumentException if + * listMinLength is negative or larger than + * listMaxLength. + */ + protected void addAttribute(String elementName, + String attrName, + int dataType, + boolean required, + int listMinLength, + int listMaxLength) { + Element element = getElement(elementName); + if (attrName == null) { + throw new IllegalArgumentException("attrName == null!"); + } + if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) { + throw new IllegalArgumentException("Invalid value for dataType!"); + } + if (listMinLength < 0 || listMinLength > listMaxLength) { + throw new IllegalArgumentException("Invalid list bounds!"); + } + + Attribute attr = new Attribute(); + attr.attrName = attrName; + attr.valueType = VALUE_LIST; + attr.dataType = dataType; + attr.required = required; + attr.listMinLength = listMinLength; + attr.listMaxLength = listMaxLength; + + element.attrList.add(attrName); + element.attrMap.put(attrName, attr); + } + + /** + * Adds a new attribute to a previously defined element that will + * be defined by the enumerated values TRUE and + * FALSE, with a datatype of + * DATATYPE_BOOLEAN. + * + * @param elementName the name of the element. + * @param attrName the name of the attribute being added. + * @param hasDefaultValue true if a default value + * should be present. + * @param defaultValue the default value for the attribute as a + * boolean, ignored if hasDefaultValue + * is false. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this + * format. + * @exception IllegalArgumentException if attrName is + * null. + */ + protected void addBooleanAttribute(String elementName, + String attrName, + boolean hasDefaultValue, + boolean defaultValue) { + List values = new ArrayList(); + values.add("TRUE"); + values.add("FALSE"); + + String dval = null; + if (hasDefaultValue) { + dval = defaultValue ? "TRUE" : "FALSE"; + } + addAttribute(elementName, + attrName, + DATATYPE_BOOLEAN, + true, + dval, + values); + } + + /** + * Removes an attribute from a previously defined element. If no + * attribute with the given name was present in the given element, + * nothing happens and no exception is thrown. + * + * @param elementName the name of the element. + * @param attrName the name of the attribute being removed. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this format. + */ + protected void removeAttribute(String elementName, String attrName) { + Element element = getElement(elementName); + element.attrList.remove(attrName); + element.attrMap.remove(attrName); + } + + /** + * Allows an Object reference of a given class type + * to be stored in nodes implementing the named element. The + * value of the Object is unconstrained other than by + * its class type. + * + *

If an Object reference was previously allowed, + * the previous settings are overwritten. + * + * @param elementName the name of the element. + * @param classType a Class variable indicating the + * legal class type for the object value. + * @param required true if an object value must be present. + * @param defaultValue the default value for the + * Object reference, or null. + * @param the type of the object. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this format. + */ + protected void addObjectValue(String elementName, + Class classType, + boolean required, + T defaultValue) + { + Element element = getElement(elementName); + ObjectValue obj = new ObjectValue(); + obj.valueType = VALUE_ARBITRARY; + obj.classType = classType; + obj.defaultValue = defaultValue; + + element.objectValue = obj; + } + + /** + * Allows an Object reference of a given class type + * to be stored in nodes implementing the named element. The + * value of the Object must be one of the values + * given by enumeratedValues. + * + *

If an Object reference was previously allowed, + * the previous settings are overwritten. + * + * @param elementName the name of the element. + * @param classType a Class variable indicating the + * legal class type for the object value. + * @param required true if an object value must be present. + * @param defaultValue the default value for the + * Object reference, or null. + * @param enumeratedValues a List of + * Objects containing the legal values for the + * object reference. + * @param the type of the object. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this format. + * @exception IllegalArgumentException if + * enumeratedValues is null. + * @exception IllegalArgumentException if + * enumeratedValues does not contain at least one + * entry. + * @exception IllegalArgumentException if + * enumeratedValues contains an element that is not + * an instance of the class type denoted by classType + * or is null. + */ + protected void addObjectValue(String elementName, + Class classType, + boolean required, + T defaultValue, + List enumeratedValues) + { + Element element = getElement(elementName); + if (enumeratedValues == null) { + throw new IllegalArgumentException("enumeratedValues == null!"); + } + if (enumeratedValues.size() == 0) { + throw new IllegalArgumentException("enumeratedValues is empty!"); + } + Iterator iter = enumeratedValues.iterator(); + while (iter.hasNext()) { + Object o = iter.next(); + if (o == null) { + throw new IllegalArgumentException("enumeratedValues contains a null!"); + } + if (!classType.isInstance(o)) { + throw new IllegalArgumentException("enumeratedValues contains a value not of class classType!"); + } + } + + ObjectValue obj = new ObjectValue(); + obj.valueType = VALUE_ENUMERATION; + obj.classType = classType; + obj.defaultValue = defaultValue; + obj.enumeratedValues = enumeratedValues; + + element.objectValue = obj; + } + + /** + * Allows an Object reference of a given class type + * to be stored in nodes implementing the named element. The + * value of the Object must be within the range given + * by minValue and maxValue. + * Furthermore, the class type must implement the + * Comparable interface. + * + *

If an Object reference was previously allowed, + * the previous settings are overwritten. + * + * @param elementName the name of the element. + * @param classType a Class variable indicating the + * legal class type for the object value. + * @param defaultValue the default value for the + * @param minValue the smallest (inclusive or exclusive depending + * on the value of minInclusive) legal value for the + * object value, as a String. + * @param maxValue the largest (inclusive or exclusive depending + * on the value of minInclusive) legal value for the + * object value, as a String. + * @param minInclusive true if minValue + * is inclusive. + * @param maxInclusive true if maxValue + * is inclusive. + * @param the type of the object. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this + * format. + */ + protected > void + addObjectValue(String elementName, + Class classType, + T defaultValue, + Comparable minValue, + Comparable maxValue, + boolean minInclusive, + boolean maxInclusive) + { + Element element = getElement(elementName); + ObjectValue obj = new ObjectValue(); + obj.valueType = VALUE_RANGE; + if (minInclusive) { + obj.valueType |= VALUE_RANGE_MIN_INCLUSIVE_MASK; + } + if (maxInclusive) { + obj.valueType |= VALUE_RANGE_MAX_INCLUSIVE_MASK; + } + obj.classType = classType; + obj.defaultValue = defaultValue; + obj.minValue = minValue; + obj.maxValue = maxValue; + + element.objectValue = obj; + } + + /** + * Allows an Object reference of a given class type + * to be stored in nodes implementing the named element. The + * value of the Object must an array of objects of + * class type given by classType, with at least + * arrayMinLength and at most + * arrayMaxLength elements. + * + *

If an Object reference was previously allowed, + * the previous settings are overwritten. + * + * @param elementName the name of the element. + * @param classType a Class variable indicating the + * legal class type for the object value. + * @param arrayMinLength the smallest legal length for the array. + * @param arrayMaxLength the largest legal length for the array. + * + * @exception IllegalArgumentException if elementName is + * not a legal element name for this format. + */ + protected void addObjectValue(String elementName, + Class classType, + int arrayMinLength, + int arrayMaxLength) { + Element element = getElement(elementName); + ObjectValue obj = new ObjectValue(); + obj.valueType = VALUE_LIST; + obj.classType = classType; + obj.arrayMinLength = arrayMinLength; + obj.arrayMaxLength = arrayMaxLength; + + element.objectValue = obj; + } + + /** + * Disallows an Object reference from being stored in + * nodes implementing the named element. + * + * @param elementName the name of the element. + * + * @exception IllegalArgumentException if elementName is + * not a legal element name for this format. + */ + protected void removeObjectValue(String elementName) { + Element element = getElement(elementName); + element.objectValue = null; + } + + // Utility method + + // Methods from IIOMetadataFormat + + // Root + + public String getRootName() { + return rootName; + } + + // Multiplicity + + public abstract boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType); + + public int getElementMinChildren(String elementName) { + Element element = getElement(elementName); + if (element.childPolicy != CHILD_POLICY_REPEAT) { + throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!"); + } + return element.minChildren; + } + + public int getElementMaxChildren(String elementName) { + Element element = getElement(elementName); + if (element.childPolicy != CHILD_POLICY_REPEAT) { + throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!"); + } + return element.maxChildren; + } + + private String getResource(String key, Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + + /** + * If an applet supplies an implementation of IIOMetadataFormat and + * resource bundles, then the resource bundle will need to be + * accessed via the applet class loader. So first try the context + * class loader to locate the resource bundle. + * If that throws MissingResourceException, then try the + * system class loader. + */ + ClassLoader loader = (ClassLoader) + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + + ResourceBundle bundle = null; + try { + bundle = ResourceBundle.getBundle(resourceBaseName, + locale, loader); + } catch (MissingResourceException mre) { + try { + bundle = ResourceBundle.getBundle(resourceBaseName, locale); + } catch (MissingResourceException mre1) { + return null; + } + } + + try { + return bundle.getString(key); + } catch (MissingResourceException e) { + return null; + } + } + + /** + * Returns a String containing a description of the + * named element, or null. The description will be + * localized for the supplied Locale if possible. + * + *

The default implementation will first locate a + * ResourceBundle using the current resource base + * name set by setResourceBaseName and the supplied + * Locale, using the fallback mechanism described in + * the comments for ResourceBundle.getBundle. If a + * ResourceBundle is found, the element name will be + * used as a key to its getString method, and the + * result returned. If no ResourceBundle is found, + * or no such key is present, null will be returned. + * + *

If locale is null, the current + * default Locale returned by Locale.getLocale + * will be used. + * + * @param elementName the name of the element. + * @param locale the Locale for which localization + * will be attempted. + * + * @return the element description. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this format. + * + * @see #setResourceBaseName + */ + public String getElementDescription(String elementName, + Locale locale) { + Element element = getElement(elementName); + return getResource(elementName, locale); + } + + // Children + + public int getChildPolicy(String elementName) { + Element element = getElement(elementName); + return element.childPolicy; + } + + public String[] getChildNames(String elementName) { + Element element = getElement(elementName); + if (element.childPolicy == CHILD_POLICY_EMPTY) { + return null; + } + return (String[])element.childList.toArray(new String[0]); + } + + // Attributes + + public String[] getAttributeNames(String elementName) { + Element element = getElement(elementName); + List names = element.attrList; + + String[] result = new String[names.size()]; + return (String[])names.toArray(result); + } + + public int getAttributeValueType(String elementName, String attrName) { + Attribute attr = getAttribute(elementName, attrName); + return attr.valueType; + } + + public int getAttributeDataType(String elementName, String attrName) { + Attribute attr = getAttribute(elementName, attrName); + return attr.dataType; + } + + public boolean isAttributeRequired(String elementName, String attrName) { + Attribute attr = getAttribute(elementName, attrName); + return attr.required; + } + + public String getAttributeDefaultValue(String elementName, + String attrName) { + Attribute attr = getAttribute(elementName, attrName); + return attr.defaultValue; + } + + public String[] getAttributeEnumerations(String elementName, + String attrName) { + Attribute attr = getAttribute(elementName, attrName); + if (attr.valueType != VALUE_ENUMERATION) { + throw new IllegalArgumentException + ("Attribute not an enumeration!"); + } + + List values = attr.enumeratedValues; + Iterator iter = values.iterator(); + String[] result = new String[values.size()]; + return (String[])values.toArray(result); + } + + public String getAttributeMinValue(String elementName, String attrName) { + Attribute attr = getAttribute(elementName, attrName); + if (attr.valueType != VALUE_RANGE && + attr.valueType != VALUE_RANGE_MIN_INCLUSIVE && + attr.valueType != VALUE_RANGE_MAX_INCLUSIVE && + attr.valueType != VALUE_RANGE_MIN_MAX_INCLUSIVE) { + throw new IllegalArgumentException("Attribute not a range!"); + } + + return attr.minValue; + } + + public String getAttributeMaxValue(String elementName, String attrName) { + Attribute attr = getAttribute(elementName, attrName); + if (attr.valueType != VALUE_RANGE && + attr.valueType != VALUE_RANGE_MIN_INCLUSIVE && + attr.valueType != VALUE_RANGE_MAX_INCLUSIVE && + attr.valueType != VALUE_RANGE_MIN_MAX_INCLUSIVE) { + throw new IllegalArgumentException("Attribute not a range!"); + } + + return attr.maxValue; + } + + public int getAttributeListMinLength(String elementName, String attrName) { + Attribute attr = getAttribute(elementName, attrName); + if (attr.valueType != VALUE_LIST) { + throw new IllegalArgumentException("Attribute not a list!"); + } + + return attr.listMinLength; + } + + public int getAttributeListMaxLength(String elementName, String attrName) { + Attribute attr = getAttribute(elementName, attrName); + if (attr.valueType != VALUE_LIST) { + throw new IllegalArgumentException("Attribute not a list!"); + } + + return attr.listMaxLength; + } + + /** + * Returns a String containing a description of the + * named attribute, or null. The description will be + * localized for the supplied Locale if possible. + * + *

The default implementation will first locate a + * ResourceBundle using the current resource base + * name set by setResourceBaseName and the supplied + * Locale, using the fallback mechanism described in + * the comments for ResourceBundle.getBundle. If a + * ResourceBundle is found, the element name followed + * by a "/" character followed by the attribute name + * (elementName + "/" + attrName) will be used as a + * key to its getString method, and the result + * returned. If no ResourceBundle is found, or no + * such key is present, null will be returned. + * + *

If locale is null, the current + * default Locale returned by Locale.getLocale + * will be used. + * + * @param elementName the name of the element. + * @param attrName the name of the attribute. + * @param locale the Locale for which localization + * will be attempted, or null. + * + * @return the attribute description. + * + * @exception IllegalArgumentException if elementName + * is null, or is not a legal element name for this format. + * @exception IllegalArgumentException if attrName is + * null or is not a legal attribute name for this + * element. + * + * @see #setResourceBaseName + */ + public String getAttributeDescription(String elementName, + String attrName, + Locale locale) { + Element element = getElement(elementName); + if (attrName == null) { + throw new IllegalArgumentException("attrName == null!"); + } + Attribute attr = (Attribute)element.attrMap.get(attrName); + if (attr == null) { + throw new IllegalArgumentException("No such attribute!"); + } + + String key = elementName + "/" + attrName; + return getResource(key, locale); + } + + private ObjectValue getObjectValue(String elementName) { + Element element = getElement(elementName); + ObjectValue objv = (ObjectValue)element.objectValue; + if (objv == null) { + throw new IllegalArgumentException("No object within element " + + elementName + "!"); + } + return objv; + } + + public int getObjectValueType(String elementName) { + Element element = getElement(elementName); + ObjectValue objv = (ObjectValue)element.objectValue; + if (objv == null) { + return VALUE_NONE; + } + return objv.valueType; + } + + public Class getObjectClass(String elementName) { + ObjectValue objv = getObjectValue(elementName); + return objv.classType; + } + + public Object getObjectDefaultValue(String elementName) { + ObjectValue objv = getObjectValue(elementName); + return objv.defaultValue; + } + + public Object[] getObjectEnumerations(String elementName) { + ObjectValue objv = getObjectValue(elementName); + if (objv.valueType != VALUE_ENUMERATION) { + throw new IllegalArgumentException("Not an enumeration!"); + } + List vlist = objv.enumeratedValues; + Object[] values = new Object[vlist.size()]; + return vlist.toArray(values); + } + + public Comparable getObjectMinValue(String elementName) { + ObjectValue objv = getObjectValue(elementName); + if ((objv.valueType & VALUE_RANGE) != VALUE_RANGE) { + throw new IllegalArgumentException("Not a range!"); + } + return objv.minValue; + } + + public Comparable getObjectMaxValue(String elementName) { + ObjectValue objv = getObjectValue(elementName); + if ((objv.valueType & VALUE_RANGE) != VALUE_RANGE) { + throw new IllegalArgumentException("Not a range!"); + } + return objv.maxValue; + } + + public int getObjectArrayMinLength(String elementName) { + ObjectValue objv = getObjectValue(elementName); + if (objv.valueType != VALUE_LIST) { + throw new IllegalArgumentException("Not a list!"); + } + return objv.arrayMinLength; + } + + public int getObjectArrayMaxLength(String elementName) { + ObjectValue objv = getObjectValue(elementName); + if (objv.valueType != VALUE_LIST) { + throw new IllegalArgumentException("Not a list!"); + } + return objv.arrayMaxLength; + } + + // Standard format descriptor + + private synchronized static void createStandardFormat() { + if (standardFormat == null) { + standardFormat = new StandardMetadataFormat(); + } + } + + /** + * Returns an IIOMetadataFormat object describing the + * standard, plug-in neutral javax.imageio_1.0 + * metadata document format described in the comment of the + * javax.imageio.metadata package. + * + * @return a predefined IIOMetadataFormat instance. + */ + public static IIOMetadataFormat getStandardFormatInstance() { + createStandardFormat(); + return standardFormat; + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataNode.java b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataNode.java new file mode 100644 index 000000000..418061453 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/metadata/IIOMetadataNode.java @@ -0,0 +1,1174 @@ +/* + * Copyright (c) 2000, 2011, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.metadata; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.DOMException; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.TypeInfo; +import org.w3c.dom.UserDataHandler; + + +class IIODOMException extends DOMException { + + public IIODOMException(short code, String message) { + super(code, message); + } +} + +class IIONamedNodeMap implements NamedNodeMap { + + List nodes; + + public IIONamedNodeMap(List nodes) { + this.nodes = nodes; + } + + public int getLength() { + return nodes.size(); + } + + public Node getNamedItem(String name) { + Iterator iter = nodes.iterator(); + while (iter.hasNext()) { + Node node = (Node)iter.next(); + if (name.equals(node.getNodeName())) { + return node; + } + } + + return null; + } + + public Node item(int index) { + Node node = (Node)nodes.get(index); + return node; + } + + public Node removeNamedItem(java.lang.String name) { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "This NamedNodeMap is read-only!"); + } + + public Node setNamedItem(Node arg) { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "This NamedNodeMap is read-only!"); + } + + /** + * Equivalent to getNamedItem(localName). + */ + public Node getNamedItemNS(String namespaceURI, String localName) { + return getNamedItem(localName); + } + + /** + * Equivalent to setNamedItem(arg). + */ + public Node setNamedItemNS(Node arg) { + return setNamedItem(arg); + } + + /** + * Equivalent to removeNamedItem(localName). + */ + public Node removeNamedItemNS(String namespaceURI, String localName) { + return removeNamedItem(localName); + } +} + +class IIONodeList implements NodeList { + + List nodes; + + public IIONodeList(List nodes) { + this.nodes = nodes; + } + + public int getLength() { + return nodes.size(); + } + + public Node item(int index) { + if (index < 0 || index > nodes.size()) { + return null; + } + return (Node)nodes.get(index); + } +} + +class IIOAttr extends IIOMetadataNode implements Attr { + + Element owner; + String name; + String value; + + public IIOAttr(Element owner, String name, String value) { + this.owner = owner; + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getNodeName() { + return name; + } + + public short getNodeType() { + return ATTRIBUTE_NODE; + } + + public boolean getSpecified() { + return true; + } + + public String getValue() { + return value; + } + + public String getNodeValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public void setNodeValue(String value) { + this.value = value; + } + + public Element getOwnerElement() { + return owner; + } + + public void setOwnerElement(Element owner) { + this.owner = owner; + } + + /** This method is new in the DOM L3 for Attr interface. + * Could throw DOMException here, but its probably OK + * to always return false. One reason for this, is we have no good + * way to document this exception, since this class, IIOAttr, + * is not a public class. The rest of the methods that throw + * DOMException are publically documented as such on IIOMetadataNode. + * @return false + */ + public boolean isId() { + return false; + } + + +} + +/** + * A class representing a node in a meta-data tree, which implements + * the + * org.w3c.dom.Element interface and additionally allows + * for the storage of non-textual objects via the + * getUserObject and setUserObject methods. + * + *

This class is not intended to be used for general XML + * processing. In particular, Element nodes created + * within the Image I/O API are not compatible with those created by + * Sun's standard implementation of the org.w3.dom API. + * In particular, the implementation is tuned for simple uses and may + * not perform well for intensive processing. + * + *

Namespaces are ignored in this implementation. The terms "tag + * name" and "node name" are always considered to be synonymous. + * + * Note: + * The DOM Level 3 specification added a number of new methods to the + * {@code Node}, {@code Element} and {@code Attr} interfaces that are not + * of value to the {@code IIOMetadataNode} implementation or specification. + * + * Calling such methods on an {@code IIOMetadataNode}, or an {@code Attr} + * instance returned from an {@code IIOMetadataNode} will result in a + * {@code DOMException} being thrown. + * + * @see IIOMetadata#getAsTree + * @see IIOMetadata#setFromTree + * @see IIOMetadata#mergeTree + * + */ +public class IIOMetadataNode implements Element, NodeList { + + /** + * The name of the node as a String. + */ + private String nodeName = null; + + /** + * The value of the node as a String. The Image I/O + * API typically does not make use of the node value. + */ + private String nodeValue = null; + + /** + * The Object value associated with this node. + */ + private Object userObject = null; + + /** + * The parent node of this node, or null if this node + * forms the root of its own tree. + */ + private IIOMetadataNode parent = null; + + /** + * The number of child nodes. + */ + private int numChildren = 0; + + /** + * The first (leftmost) child node of this node, or + * null if this node is a leaf node. + */ + private IIOMetadataNode firstChild = null; + + /** + * The last (rightmost) child node of this node, or + * null if this node is a leaf node. + */ + private IIOMetadataNode lastChild = null; + + /** + * The next (right) sibling node of this node, or + * null if this node is its parent's last child node. + */ + private IIOMetadataNode nextSibling = null; + + /** + * The previous (left) sibling node of this node, or + * null if this node is its parent's first child node. + */ + private IIOMetadataNode previousSibling = null; + + /** + * A List of IIOAttr nodes representing + * attributes. + */ + private List attributes = new ArrayList(); + + /** + * Constructs an empty IIOMetadataNode. + */ + public IIOMetadataNode() {} + + /** + * Constructs an IIOMetadataNode with a given node + * name. + * + * @param nodeName the name of the node, as a String. + */ + public IIOMetadataNode(String nodeName) { + this.nodeName = nodeName; + } + + /** + * Check that the node is either null or an + * IIOMetadataNode. + */ + private void checkNode(Node node) throws DOMException { + if (node == null) { + return; + } + if (!(node instanceof IIOMetadataNode)) { + throw new IIODOMException(DOMException.WRONG_DOCUMENT_ERR, + "Node not an IIOMetadataNode!"); + } + } + + // Methods from Node + + /** + * Returns the node name associated with this node. + * + * @return the node name, as a String. + */ + public String getNodeName() { + return nodeName; + } + + /** + * Returns the value associated with this node. + * + * @return the node value, as a String. + */ + public String getNodeValue(){ + return nodeValue; + } + + /** + * Sets the String value associated with this node. + */ + public void setNodeValue(String nodeValue) { + this.nodeValue = nodeValue; + } + + /** + * Returns the node type, which is always + * ELEMENT_NODE. + * + * @return the short value ELEMENT_NODE. + */ + public short getNodeType() { + return ELEMENT_NODE; + } + + /** + * Returns the parent of this node. A null value + * indicates that the node is the root of its own tree. To add a + * node to an existing tree, use one of the + * insertBefore, replaceChild, or + * appendChild methods. + * + * @return the parent, as a Node. + * + * @see #insertBefore + * @see #replaceChild + * @see #appendChild + */ + public Node getParentNode() { + return parent; + } + + /** + * Returns a NodeList that contains all children of this node. + * If there are no children, this is a NodeList containing + * no nodes. + * + * @return the children as a NodeList + */ + public NodeList getChildNodes() { + return this; + } + + /** + * Returns the first child of this node, or null if + * the node has no children. + * + * @return the first child, as a Node, or + * null + */ + public Node getFirstChild() { + return firstChild; + } + + /** + * Returns the last child of this node, or null if + * the node has no children. + * + * @return the last child, as a Node, or + * null. + */ + public Node getLastChild() { + return lastChild; + } + + /** + * Returns the previous sibling of this node, or null + * if this node has no previous sibling. + * + * @return the previous sibling, as a Node, or + * null. + */ + public Node getPreviousSibling() { + return previousSibling; + } + + /** + * Returns the next sibling of this node, or null if + * the node has no next sibling. + * + * @return the next sibling, as a Node, or + * null. + */ + public Node getNextSibling() { + return nextSibling; + } + + /** + * Returns a NamedNodeMap containing the attributes of + * this node. + * + * @return a NamedNodeMap containing the attributes of + * this node. + */ + public NamedNodeMap getAttributes() { + return new IIONamedNodeMap(attributes); + } + + /** + * Returns null, since IIOMetadataNodes + * do not belong to any Document. + * + * @return null. + */ + public Document getOwnerDocument() { + return null; + } + + /** + * Inserts the node newChild before the existing + * child node refChild. If refChild is + * null, insert newChild at the end of + * the list of children. + * + * @param newChild the Node to insert. + * @param refChild the reference Node. + * + * @return the node being inserted. + * + * @exception IllegalArgumentException if newChild is + * null. + */ + public Node insertBefore(Node newChild, + Node refChild) { + if (newChild == null) { + throw new IllegalArgumentException("newChild == null!"); + } + + checkNode(newChild); + checkNode(refChild); + + IIOMetadataNode newChildNode = (IIOMetadataNode)newChild; + IIOMetadataNode refChildNode = (IIOMetadataNode)refChild; + + // Siblings, can be null. + IIOMetadataNode previous = null; + IIOMetadataNode next = null; + + if (refChild == null) { + previous = this.lastChild; + next = null; + this.lastChild = newChildNode; + } else { + previous = refChildNode.previousSibling; + next = refChildNode; + } + + if (previous != null) { + previous.nextSibling = newChildNode; + } + if (next != null) { + next.previousSibling = newChildNode; + } + + newChildNode.parent = this; + newChildNode.previousSibling = previous; + newChildNode.nextSibling = next; + + // N.B.: O.K. if refChild == null + if (this.firstChild == refChildNode) { + this.firstChild = newChildNode; + } + + ++numChildren; + return newChildNode; + } + + /** + * Replaces the child node oldChild with + * newChild in the list of children, and returns the + * oldChild node. + * + * @param newChild the Node to insert. + * @param oldChild the Node to be replaced. + * + * @return the node replaced. + * + * @exception IllegalArgumentException if newChild is + * null. + */ + public Node replaceChild(Node newChild, + Node oldChild) { + if (newChild == null) { + throw new IllegalArgumentException("newChild == null!"); + } + + checkNode(newChild); + checkNode(oldChild); + + IIOMetadataNode newChildNode = (IIOMetadataNode)newChild; + IIOMetadataNode oldChildNode = (IIOMetadataNode)oldChild; + + IIOMetadataNode previous = oldChildNode.previousSibling; + IIOMetadataNode next = oldChildNode.nextSibling; + + if (previous != null) { + previous.nextSibling = newChildNode; + } + if (next != null) { + next.previousSibling = newChildNode; + } + + newChildNode.parent = this; + newChildNode.previousSibling = previous; + newChildNode.nextSibling = next; + + if (firstChild == oldChildNode) { + firstChild = newChildNode; + } + if (lastChild == oldChildNode) { + lastChild = newChildNode; + } + + oldChildNode.parent = null; + oldChildNode.previousSibling = null; + oldChildNode.nextSibling = null; + + return oldChildNode; + } + + /** + * Removes the child node indicated by oldChild from + * the list of children, and returns it. + * + * @param oldChild the Node to be removed. + * + * @return the node removed. + * + * @exception IllegalArgumentException if oldChild is + * null. + */ + public Node removeChild(Node oldChild) { + if (oldChild == null) { + throw new IllegalArgumentException("oldChild == null!"); + } + checkNode(oldChild); + + IIOMetadataNode oldChildNode = (IIOMetadataNode)oldChild; + + IIOMetadataNode previous = oldChildNode.previousSibling; + IIOMetadataNode next = oldChildNode.nextSibling; + + if (previous != null) { + previous.nextSibling = next; + } + if (next != null) { + next.previousSibling = previous; + } + + if (this.firstChild == oldChildNode) { + this.firstChild = next; + } + if (this.lastChild == oldChildNode) { + this.lastChild = previous; + } + + oldChildNode.parent = null; + oldChildNode.previousSibling = null; + oldChildNode.nextSibling = null; + + --numChildren; + return oldChildNode; + } + + /** + * Adds the node newChild to the end of the list of + * children of this node. + * + * @param newChild the Node to insert. + * + * @return the node added. + * + * @exception IllegalArgumentException if newChild is + * null. + */ + public Node appendChild(Node newChild) { + if (newChild == null) { + throw new IllegalArgumentException("newChild == null!"); + } + checkNode(newChild); + + // insertBefore will increment numChildren + return insertBefore(newChild, null); + } + + /** + * Returns true if this node has child nodes. + * + * @return true if this node has children. + */ + public boolean hasChildNodes() { + return numChildren > 0; + } + + /** + * Returns a duplicate of this node. The duplicate node has no + * parent (getParentNode returns null). + * If a shallow clone is being performed (deep is + * false), the new node will not have any children or + * siblings. If a deep clone is being performed, the new node + * will form the root of a complete cloned subtree. + * + * @param deep if true, recursively clone the subtree + * under the specified node; if false, clone only the + * node itself. + * + * @return the duplicate node. + */ + public Node cloneNode(boolean deep) { + IIOMetadataNode newNode = new IIOMetadataNode(this.nodeName); + newNode.setUserObject(getUserObject()); + // Attributes + + if (deep) { + for (IIOMetadataNode child = firstChild; + child != null; + child = child.nextSibling) { + newNode.appendChild(child.cloneNode(true)); + } + } + + return newNode; + } + + /** + * Does nothing, since IIOMetadataNodes do not + * contain Text children. + */ + public void normalize() { + } + + /** + * Returns false since DOM features are not + * supported. + * + * @return false. + * + * @param feature a String, which is ignored. + * @param version a String, which is ignored. + */ + public boolean isSupported(String feature, String version) { + return false; + } + + /** + * Returns null, since namespaces are not supported. + */ + public String getNamespaceURI() throws DOMException { + return null; + } + + /** + * Returns null, since namespaces are not supported. + * + * @return null. + * + * @see #setPrefix + */ + public String getPrefix() { + return null; + } + + /** + * Does nothing, since namespaces are not supported. + * + * @param prefix a String, which is ignored. + * + * @see #getPrefix + */ + public void setPrefix(String prefix) { + } + + /** + * Equivalent to getNodeName. + * + * @return the node name, as a String. + */ + public String getLocalName() { + return nodeName; + } + + // Methods from Element + + + /** + * Equivalent to getNodeName. + * + * @return the node name, as a String + */ + public String getTagName() { + return nodeName; + } + + /** + * Retrieves an attribute value by name. + * @param name The name of the attribute to retrieve. + * @return The Attr value as a string, or the empty string + * if that attribute does not have a specified or default value. + */ + public String getAttribute(String name) { + Attr attr = getAttributeNode(name); + if (attr == null) { + return ""; + } + return attr.getValue(); + } + + /** + * Equivalent to getAttribute(localName). + * + * @see #setAttributeNS + */ + public String getAttributeNS(String namespaceURI, String localName) { + return getAttribute(localName); + } + + public void setAttribute(String name, String value) { + // Name must be valid unicode chars + boolean valid = true; + char[] chs = name.toCharArray(); + for (int i=0;i= 0xfffe) { + valid = false; + break; + } + } + if (!valid) { + throw new IIODOMException(DOMException.INVALID_CHARACTER_ERR, + "Attribute name is illegal!"); + } + removeAttribute(name, false); + attributes.add(new IIOAttr(this, name, value)); + } + + /** + * Equivalent to setAttribute(qualifiedName, value). + * + * @see #getAttributeNS + */ + public void setAttributeNS(String namespaceURI, + String qualifiedName, String value) { + setAttribute(qualifiedName, value); + } + + public void removeAttribute(String name) { + removeAttribute(name, true); + } + + private void removeAttribute(String name, boolean checkPresent) { + int numAttributes = attributes.size(); + for (int i = 0; i < numAttributes; i++) { + IIOAttr attr = (IIOAttr)attributes.get(i); + if (name.equals(attr.getName())) { + attr.setOwnerElement(null); + attributes.remove(i); + return; + } + } + + // If we get here, the attribute doesn't exist + if (checkPresent) { + throw new IIODOMException(DOMException.NOT_FOUND_ERR, + "No such attribute!"); + } + } + + /** + * Equivalent to removeAttribute(localName). + */ + public void removeAttributeNS(String namespaceURI, + String localName) { + removeAttribute(localName); + } + + public Attr getAttributeNode(String name) { + Node node = getAttributes().getNamedItem(name); + return (Attr)node; + } + + /** + * Equivalent to getAttributeNode(localName). + * + * @see #setAttributeNodeNS + */ + public Attr getAttributeNodeNS(String namespaceURI, + String localName) { + return getAttributeNode(localName); + } + + public Attr setAttributeNode(Attr newAttr) throws DOMException { + Element owner = newAttr.getOwnerElement(); + if (owner != null) { + if (owner == this) { + return null; + } else { + throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR, + "Attribute is already in use"); + } + } + + IIOAttr attr; + if (newAttr instanceof IIOAttr) { + attr = (IIOAttr)newAttr; + attr.setOwnerElement(this); + } else { + attr = new IIOAttr(this, + newAttr.getName(), + newAttr.getValue()); + } + + Attr oldAttr = getAttributeNode(attr.getName()); + if (oldAttr != null) { + removeAttributeNode(oldAttr); + } + + attributes.add(attr); + + return oldAttr; + } + + /** + * Equivalent to setAttributeNode(newAttr). + * + * @see #getAttributeNodeNS + */ + public Attr setAttributeNodeNS(Attr newAttr) { + return setAttributeNode(newAttr); + } + + public Attr removeAttributeNode(Attr oldAttr) { + removeAttribute(oldAttr.getName()); + return oldAttr; + } + + public NodeList getElementsByTagName(String name) { + List l = new ArrayList(); + getElementsByTagName(name, l); + return new IIONodeList(l); + } + + private void getElementsByTagName(String name, List l) { + if (nodeName.equals(name)) { + l.add(this); + } + + Node child = getFirstChild(); + while (child != null) { + ((IIOMetadataNode)child).getElementsByTagName(name, l); + child = child.getNextSibling(); + } + } + + /** + * Equivalent to getElementsByTagName(localName). + */ + public NodeList getElementsByTagNameNS(String namespaceURI, + String localName) { + return getElementsByTagName(localName); + } + + public boolean hasAttributes() { + return attributes.size() > 0; + } + + public boolean hasAttribute(String name) { + return getAttributeNode(name) != null; + } + + /** + * Equivalent to hasAttribute(localName). + */ + public boolean hasAttributeNS(String namespaceURI, + String localName) { + return hasAttribute(localName); + } + + // Methods from NodeList + + public int getLength() { + return numChildren; + } + + public Node item(int index) { + if (index < 0) { + return null; + } + + Node child = getFirstChild(); + while (child != null && index-- > 0) { + child = child.getNextSibling(); + } + return child; + } + + /** + * Returns the Object value associated with this node. + * + * @return the user Object. + * + * @see #setUserObject + */ + public Object getUserObject() { + return userObject; + } + + /** + * Sets the value associated with this node. + * + * @param userObject the user Object. + * + * @see #getUserObject + */ + public void setUserObject(Object userObject) { + this.userObject = userObject; + } + + // Start of dummy methods for DOM L3. + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public void setIdAttribute(String name, + boolean isId) + throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public void setIdAttributeNS(String namespaceURI, + String localName, + boolean isId) + throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public void setIdAttributeNode(Attr idAttr, + boolean isId) + throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public TypeInfo getSchemaTypeInfo() throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public Object setUserData(String key, + Object data, + UserDataHandler handler) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public Object getUserData(String key) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public Object getFeature(String feature, String version) + throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public boolean isSameNode(Node node) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public boolean isEqualNode(Node node) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public String lookupNamespaceURI(String prefix) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public boolean isDefaultNamespace(String namespaceURI) + throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public String lookupPrefix(String namespaceURI) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public String getTextContent() throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public void setTextContent(String textContent) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public short compareDocumentPosition(Node other) + throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + + /** + * This DOM Level 3 method is not supported for {@code IIOMetadataNode} + * and will throw a {@code DOMException}. + * @throws DOMException - always. + */ + public String getBaseURI() throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, + "Method not supported"); + } + //End of dummy methods for DOM L3. + + @Override + public boolean supports(String feature, String version) { + // TODO Auto-generated method stub + return false; + } + + @Override + public int compareDocumentOrder(Node other) throws DOMException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int compareTreePosition(Node other) throws DOMException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String lookupNamespacePrefix(String namespaceURI) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void normalizeNS() { + // TODO Auto-generated method stub + + } + + @Override + public Object setUserData(Object data, String key) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Object getKey() { + // TODO Auto-generated method stub + return null; + } + + +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/bmp/BMPImageWriteParam.java b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/bmp/BMPImageWriteParam.java new file mode 100644 index 000000000..75b23fdfa --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/bmp/BMPImageWriteParam.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.plugins.bmp; + +import java.util.Locale; +import javax.imageio.ImageWriteParam; + +import com.sun.imageio.plugins.bmp.BMPConstants; +import com.sun.imageio.plugins.bmp.BMPCompressionTypes; + +/** + * A subclass of ImageWriteParam for encoding images in + * the BMP format. + * + *

This class allows for the specification of various parameters + * while writing a BMP format image file. By default, the data layout + * is bottom-up, such that the pixels are stored in bottom-up order, + * the first scanline being stored last. + * + *

The particular compression scheme to be used can be specified by using + * the setCompressionType() method with the appropriate type + * string. The compression scheme specified will be honored if and only if it + * is compatible with the type of image being written. If the specified + * compression scheme is not compatible with the type of image being written + * then the IOException will be thrown by the BMP image writer. + * If the compression type is not set explicitly then getCompressionType() + * will return null. In this case the BMP image writer will select + * a compression type that supports encoding of the given image without loss + * of the color resolution. + *

The compression type strings and the image type(s) each supports are + * listed in the following + * table: + * + *

+ * + * + * + * + * + * + *
Compression Types
Type String Description Image Types
BI_RGB Uncompressed RLE {@literal <= } 8-bits/sample
BI_RLE8 8-bit Run Length Encoding {@literal <=} 8-bits/sample
BI_RLE4 4-bit Run Length Encoding {@literal <=} 4-bits/sample
BI_BITFIELDS Packed data 16 or 32 bits/sample
+ */ +public class BMPImageWriteParam extends ImageWriteParam { + + private boolean topDown = false; + + /** + * Constructs a BMPImageWriteParam set to use a given + * Locale and with default values for all parameters. + * + * @param locale a Locale to be used to localize + * compression type names and quality descriptions, or + * null. + */ + public BMPImageWriteParam(Locale locale) { + super(locale); + + // Set compression types ("BI_RGB" denotes uncompressed). + compressionTypes = BMPCompressionTypes.getCompressionTypes(); + + // Set compression flag. + canWriteCompressed = true; + compressionMode = MODE_COPY_FROM_METADATA; + compressionType = compressionTypes[BMPConstants.BI_RGB]; + } + + /** + * Constructs an BMPImageWriteParam object with default + * values for all parameters and a null Locale. + */ + public BMPImageWriteParam() { + this(null); + } + + /** + * If set, the data will be written out in a top-down manner, the first + * scanline being written first. + * + * @param topDown whether the data are written in top-down order. + */ + public void setTopDown(boolean topDown) { + this.topDown = topDown; + } + + /** + * Returns the value of the topDown parameter. + * The default is false. + * + * @return whether the data are written in top-down order. + */ + public boolean isTopDown() { + return topDown; + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/bmp/package.html b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/bmp/package.html new file mode 100644 index 000000000..36467da7f --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/bmp/package.html @@ -0,0 +1,37 @@ + + + + + + + + + +Package containing the public classes used by the built-in BMP plug-in. + +@since 1.5 + + diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGHuffmanTable.java b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGHuffmanTable.java new file mode 100644 index 000000000..d300bf662 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGHuffmanTable.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2007, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.plugins.jpeg; + +import java.util.Arrays; + +/** + * A class encapsulating a single JPEG Huffman table. + * Fields are provided for the "standard" tables taken + * from Annex K of the JPEG specification. + * These are the tables used as defaults. + *

+ * For more information about the operation of the standard JPEG plug-in, + * see the JPEG + * metadata format specification and usage notes + */ + +public class JPEGHuffmanTable { + + /* The data for the publically defined tables, as specified in ITU T.81 + * JPEG specification section K3.3 and used in the IJG library. + */ + private static final short[] StdDCLuminanceLengths = { + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static final short[] StdDCLuminanceValues = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, + }; + + private static final short[] StdDCChrominanceLengths = { + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + private static final short[] StdDCChrominanceValues = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, + }; + + private static final short[] StdACLuminanceLengths = { + 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, + 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, + }; + + private static final short[] StdACLuminanceValues = { + 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, + }; + + private static final short[] StdACChrominanceLengths = { + 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, + 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, + }; + + private static final short[] StdACChrominanceValues = { + 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, + }; + + /** + * The standard DC luminance Huffman table. + */ + public static final JPEGHuffmanTable + StdDCLuminance = new JPEGHuffmanTable(StdDCLuminanceLengths, + StdDCLuminanceValues, false); + + /** + * The standard DC chrominance Huffman table. + */ + public static final JPEGHuffmanTable + StdDCChrominance = new JPEGHuffmanTable(StdDCChrominanceLengths, + StdDCChrominanceValues, false); + + /** + * The standard AC luminance Huffman table. + */ + public static final JPEGHuffmanTable + StdACLuminance = new JPEGHuffmanTable(StdACLuminanceLengths, + StdACLuminanceValues, false); + + /** + * The standard AC chrominance Huffman table. + */ + public static final JPEGHuffmanTable + StdACChrominance = new JPEGHuffmanTable(StdACChrominanceLengths, + StdACChrominanceValues, false); + + private short[] lengths; + private short[] values; + + /** + * Creates a Huffman table and initializes it. The input arrays are copied. + * The arrays must describe a possible Huffman table. + * For example, 3 codes cannot be expressed with a single bit. + * + * @param lengths an array of {@code short}s where lengths[k] + * is equal to the number of values with corresponding codes of + * length k + 1 bits. + * @param values an array of shorts containing the values in + * order of increasing code length. + * @throws IllegalArgumentException if lengths or + * values are null, the length of lengths is + * greater than 16, the length of values is greater than 256, + * if any value in lengths or values is less + * than zero, or if the arrays do not describe a valid Huffman table. + */ + public JPEGHuffmanTable(short[] lengths, short[] values) { + if (lengths == null || values == null || + lengths.length == 0 || values.length == 0 || + lengths.length > 16 || values.length > 256) { + throw new IllegalArgumentException("Illegal lengths or values"); + } + for (int i = 0; ishorts containing the number of values + * for each length in the Huffman table. The returned array is a copy. + * + * @return a short array where array[k-1] + * is equal to the number of values in the table of length k. + * @see #getValues + */ + public short[] getLengths() { + return Arrays.copyOf(lengths, lengths.length); + } + + /** + * Returns an array of shorts containing the values arranged + * by increasing length of their corresponding codes. + * The interpretation of the array is dependent on the values returned + * from getLengths. The returned array is a copy. + * + * @return a short array of values. + * @see #getLengths + */ + public short[] getValues() { + return Arrays.copyOf(values, values.length); + } + + /** + * Returns a {@code String} representing this Huffman table. + * @return a {@code String} representing this Huffman table. + */ + public String toString() { + String ls = System.getProperty("line.separator", "\n"); + StringBuilder sb = new StringBuilder("JPEGHuffmanTable"); + sb.append(ls).append("lengths:"); + for (int i=0; igetDefaultImageReadParam methods of the built-in JPEG + * ImageReader. + * + *

The sole purpose of these additions is to allow the + * specification of tables for use in decoding abbreviated streams. + * The built-in JPEG reader will also accept an ordinary + * ImageReadParam, which is sufficient for decoding + * non-abbreviated streams. + * + *

While tables for abbreviated streams are often obtained by + * first reading another abbreviated stream containing only the + * tables, in some applications the tables are fixed ahead of time. + * This class allows the tables to be specified directly from client + * code. If no tables are specified either in the stream or in a + * JPEGImageReadParam, then the stream is presumed to use + * the "standard" visually lossless tables. See {@link JPEGQTable JPEGQTable} + * and {@link JPEGHuffmanTable JPEGHuffmanTable} for more information + * on the default tables. + * + *

The default JPEGImageReadParam returned by the + * getDefaultReadParam method of the builtin JPEG reader + * contains no tables. Default tables may be obtained from the table + * classes {@link JPEGQTable JPEGQTable} and + * {@link JPEGHuffmanTable JPEGHuffmanTable}. + * + *

If a stream does contain tables, the tables given in a + * JPEGImageReadParam are ignored. Furthermore, if the + * first image in a stream does contain tables and subsequent ones do + * not, then the tables given in the first image are used for all the + * abbreviated images. Once tables have been read from a stream, they + * can be overridden only by tables subsequently read from the same + * stream. In order to specify new tables, the {@link + * javax.imageio.ImageReader#setInput setInput} method of + * the reader must be called to change the stream. + * + *

Note that this class does not provide a means for obtaining the + * tables found in a stream. These may be extracted from a stream by + * consulting the IIOMetadata object returned by the reader. + * + *

+ * For more information about the operation of the built-in JPEG plug-ins, + * see the JPEG + * metadata format specification and usage notes. + * + */ +public class JPEGImageReadParam extends ImageReadParam { + + private JPEGQTable[] qTables = null; + private JPEGHuffmanTable[] DCHuffmanTables = null; + private JPEGHuffmanTable[] ACHuffmanTables = null; + + /** + * Constructs a JPEGImageReadParam. + */ + public JPEGImageReadParam() { + super(); + } + + /** + * Returns true if tables are currently set. + * + * @return true if tables are present. + */ + public boolean areTablesSet() { + return (qTables != null); + } + + /** + * Sets the quantization and Huffman tables to use in decoding + * abbreviated streams. There may be a maximum of 4 tables of + * each type. These tables are ignored once tables are + * encountered in the stream. All arguments must be + * non-null. The two arrays of Huffman tables must + * have the same number of elements. The table specifiers in the + * frame and scan headers in the stream are assumed to be + * equivalent to indices into these arrays. The argument arrays + * are copied by this method. + * + * @param qTables an array of quantization table objects. + * @param DCHuffmanTables an array of Huffman table objects. + * @param ACHuffmanTables an array of Huffman table objects. + * + * @exception IllegalArgumentException if any of the arguments + * is null, has more than 4 elements, or if the + * numbers of DC and AC tables differ. + * + * @see #unsetDecodeTables + */ + public void setDecodeTables(JPEGQTable[] qTables, + JPEGHuffmanTable[] DCHuffmanTables, + JPEGHuffmanTable[] ACHuffmanTables) { + if ((qTables == null) || + (DCHuffmanTables == null) || + (ACHuffmanTables == null) || + (qTables.length > 4) || + (DCHuffmanTables.length > 4) || + (ACHuffmanTables.length > 4) || + (DCHuffmanTables.length != ACHuffmanTables.length)) { + throw new IllegalArgumentException + ("Invalid JPEG table arrays"); + } + this.qTables = (JPEGQTable[])qTables.clone(); + this.DCHuffmanTables = (JPEGHuffmanTable[])DCHuffmanTables.clone(); + this.ACHuffmanTables = (JPEGHuffmanTable[])ACHuffmanTables.clone(); + } + + /** + * Removes any quantization and Huffman tables that are currently + * set. + * + * @see #setDecodeTables + */ + public void unsetDecodeTables() { + this.qTables = null; + this.DCHuffmanTables = null; + this.ACHuffmanTables = null; + } + + /** + * Returns a copy of the array of quantization tables set on the + * most recent call to setDecodeTables, or + * null if tables are not currently set. + * + * @return an array of JPEGQTable objects, or + * null. + * + * @see #setDecodeTables + */ + public JPEGQTable[] getQTables() { + return (qTables != null) ? (JPEGQTable[])qTables.clone() : null; + } + + /** + * Returns a copy of the array of DC Huffman tables set on the + * most recent call to setDecodeTables, or + * null if tables are not currently set. + * + * @return an array of JPEGHuffmanTable objects, or + * null. + * + * @see #setDecodeTables + */ + public JPEGHuffmanTable[] getDCHuffmanTables() { + return (DCHuffmanTables != null) + ? (JPEGHuffmanTable[])DCHuffmanTables.clone() + : null; + } + + /** + * Returns a copy of the array of AC Huffman tables set on the + * most recent call to setDecodeTables, or + * null if tables are not currently set. + * + * @return an array of JPEGHuffmanTable objects, or + * null. + * + * @see #setDecodeTables + */ + public JPEGHuffmanTable[] getACHuffmanTables() { + return (ACHuffmanTables != null) + ? (JPEGHuffmanTable[])ACHuffmanTables.clone() + : null; + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGImageWriteParam.java b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGImageWriteParam.java new file mode 100644 index 000000000..5728787e9 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGImageWriteParam.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.plugins.jpeg; + +import java.util.Locale; +import javax.imageio.ImageWriteParam; + +import com.sun.imageio.plugins.jpeg.JPEG; + +/** + * This class adds the ability to set JPEG quantization and Huffman + * tables when using the built-in JPEG writer plug-in, and to request that + * optimized Huffman tables be computed for an image. An instance of + * this class will be returned from the + * getDefaultImageWriteParam methods of the built-in JPEG + * ImageWriter. + + *

The principal purpose of these additions is to allow the + * specification of tables to use in encoding abbreviated streams. + * The built-in JPEG writer will also accept an ordinary + * ImageWriteParam, in which case the writer will + * construct the necessary tables internally. + * + *

In either case, the quality setting in an ImageWriteParam + * has the same meaning as for the underlying library: 1.00 means a + * quantization table of all 1's, 0.75 means the "standard", visually + * lossless quantization table, and 0.00 means aquantization table of + * all 255's. + * + *

While tables for abbreviated streams are often specified by + * first writing an abbreviated stream containing only the tables, in + * some applications the tables are fixed ahead of time. This class + * allows the tables to be specified directly from client code. + * + *

Normally, the tables are specified in the + * IIOMetadata objects passed in to the writer, and any + * tables included in these objects are written to the stream. + * If no tables are specified in the metadata, then an abbreviated + * stream is written. If no tables are included in the metadata and + * no tables are specified in a JPEGImageWriteParam, then + * an abbreviated stream is encoded using the "standard" visually + * lossless tables. This class is necessary for specifying tables + * when an abbreviated stream must be written without writing any tables + * to a stream first. In order to use this class, the metadata object + * passed into the writer must contain no tables, and no stream metadata + * must be provided. See {@link JPEGQTable JPEGQTable} and + * {@link JPEGHuffmanTable JPEGHuffmanTable} for more + * information on the default tables. + * + *

The default JPEGImageWriteParam returned by the + * getDefaultWriteParam method of the writer contains no + * tables. Default tables are included in the default + * IIOMetadata objects returned by the writer. + * + *

If the metadata does contain tables, the tables given in a + * JPEGImageWriteParam are ignored. Furthermore, once a + * set of tables has been written, only tables in the metadata can + * override them for subsequent writes, whether to the same stream or + * a different one. In order to specify new tables using this class, + * the {@link javax.imageio.ImageWriter#reset reset} + * method of the writer must be called. + * + *

+ * For more information about the operation of the built-in JPEG plug-ins, + * see the JPEG + * metadata format specification and usage notes. + * + */ +public class JPEGImageWriteParam extends ImageWriteParam { + + private JPEGQTable[] qTables = null; + private JPEGHuffmanTable[] DCHuffmanTables = null; + private JPEGHuffmanTable[] ACHuffmanTables = null; + private boolean optimizeHuffman = false; + private String[] compressionNames = {"JPEG"}; + private float[] qualityVals = { 0.00F, 0.30F, 0.75F, 1.00F }; + private String[] qualityDescs = { + "Low quality", // 0.00 -> 0.30 + "Medium quality", // 0.30 -> 0.75 + "Visually lossless" // 0.75 -> 1.00 + }; + + /** + * Constructs a JPEGImageWriteParam. Tiling is not + * supported. Progressive encoding is supported. The default + * progressive mode is MODE_DISABLED. A single form of compression, + * named "JPEG", is supported. The default compression quality is + * 0.75. + * + * @param locale a Locale to be used by the + * superclass to localize compression type names and quality + * descriptions, or null. + */ + public JPEGImageWriteParam(Locale locale) { + super(locale); + this.canWriteProgressive = true; + this.progressiveMode = MODE_DISABLED; + this.canWriteCompressed = true; + this.compressionTypes = compressionNames; + this.compressionType = compressionTypes[0]; + this.compressionQuality = JPEG.DEFAULT_QUALITY; + } + + /** + * Removes any previous compression quality setting. + * + *

The default implementation resets the compression quality + * to 0.75F. + * + * @exception IllegalStateException if the compression mode is not + * MODE_EXPLICIT. + */ + public void unsetCompression() { + if (getCompressionMode() != MODE_EXPLICIT) { + throw new IllegalStateException + ("Compression mode not MODE_EXPLICIT!"); + } + this.compressionQuality = JPEG.DEFAULT_QUALITY; + } + + /** + * Returns false since the JPEG plug-in only supports + * lossy compression. + * + * @return false. + * + * @exception IllegalStateException if the compression mode is not + * MODE_EXPLICIT. + */ + public boolean isCompressionLossless() { + if (getCompressionMode() != MODE_EXPLICIT) { + throw new IllegalStateException + ("Compression mode not MODE_EXPLICIT!"); + } + return false; + } + + public String[] getCompressionQualityDescriptions() { + if (getCompressionMode() != MODE_EXPLICIT) { + throw new IllegalStateException + ("Compression mode not MODE_EXPLICIT!"); + } + if ((getCompressionTypes() != null) && + (getCompressionType() == null)) { + throw new IllegalStateException("No compression type set!"); + } + return (String[])qualityDescs.clone(); + } + + public float[] getCompressionQualityValues() { + if (getCompressionMode() != MODE_EXPLICIT) { + throw new IllegalStateException + ("Compression mode not MODE_EXPLICIT!"); + } + if ((getCompressionTypes() != null) && + (getCompressionType() == null)) { + throw new IllegalStateException("No compression type set!"); + } + return (float[])qualityVals.clone(); + } + /** + * Returns true if tables are currently set. + * + * @return true if tables are present. + */ + public boolean areTablesSet() { + return (qTables != null); + } + + /** + * Sets the quantization and Huffman tables to use in encoding + * abbreviated streams. There may be a maximum of 4 tables of + * each type. These tables are ignored if tables are specified in + * the metadata. All arguments must be non-null. + * The two arrays of Huffman tables must have the same number of + * elements. The table specifiers in the frame and scan headers + * in the metadata are assumed to be equivalent to indices into + * these arrays. The argument arrays are copied by this method. + * + * @param qTables An array of quantization table objects. + * @param DCHuffmanTables An array of Huffman table objects. + * @param ACHuffmanTables An array of Huffman table objects. + * + * @exception IllegalArgumentException if any of the arguments + * is null or has more than 4 elements, or if the + * numbers of DC and AC tables differ. + * + * @see #unsetEncodeTables + */ + public void setEncodeTables(JPEGQTable[] qTables, + JPEGHuffmanTable[] DCHuffmanTables, + JPEGHuffmanTable[] ACHuffmanTables) { + if ((qTables == null) || + (DCHuffmanTables == null) || + (ACHuffmanTables == null) || + (qTables.length > 4) || + (DCHuffmanTables.length > 4) || + (ACHuffmanTables.length > 4) || + (DCHuffmanTables.length != ACHuffmanTables.length)) { + throw new IllegalArgumentException("Invalid JPEG table arrays"); + } + this.qTables = (JPEGQTable[])qTables.clone(); + this.DCHuffmanTables = (JPEGHuffmanTable[])DCHuffmanTables.clone(); + this.ACHuffmanTables = (JPEGHuffmanTable[])ACHuffmanTables.clone(); + } + + /** + * Removes any quantization and Huffman tables that are currently + * set. + * + * @see #setEncodeTables + */ + public void unsetEncodeTables() { + this.qTables = null; + this.DCHuffmanTables = null; + this.ACHuffmanTables = null; + } + + /** + * Returns a copy of the array of quantization tables set on the + * most recent call to setEncodeTables, or + * null if tables are not currently set. + * + * @return an array of JPEGQTable objects, or + * null. + * + * @see #setEncodeTables + */ + public JPEGQTable[] getQTables() { + return (qTables != null) ? (JPEGQTable[])qTables.clone() : null; + } + + /** + * Returns a copy of the array of DC Huffman tables set on the + * most recent call to setEncodeTables, or + * null if tables are not currently set. + * + * @return an array of JPEGHuffmanTable objects, or + * null. + * + * @see #setEncodeTables + */ + public JPEGHuffmanTable[] getDCHuffmanTables() { + return (DCHuffmanTables != null) + ? (JPEGHuffmanTable[])DCHuffmanTables.clone() + : null; + } + + /** + * Returns a copy of the array of AC Huffman tables set on the + * most recent call to setEncodeTables, or + * null if tables are not currently set. + * + * @return an array of JPEGHuffmanTable objects, or + * null. + * + * @see #setEncodeTables + */ + public JPEGHuffmanTable[] getACHuffmanTables() { + return (ACHuffmanTables != null) + ? (JPEGHuffmanTable[])ACHuffmanTables.clone() + : null; + } + + /** + * Tells the writer to generate optimized Huffman tables + * for the image as part of the writing process. The + * default is false. If this flag is set + * to true, it overrides any tables specified + * in the metadata. Note that this means that any image + * written with this flag set to true will + * always contain Huffman tables. + * + * @param optimize A boolean indicating whether to generate + * optimized Huffman tables when writing. + * + * @see #getOptimizeHuffmanTables + */ + public void setOptimizeHuffmanTables(boolean optimize) { + optimizeHuffman = optimize; + } + + /** + * Returns the value passed into the most recent call + * to setOptimizeHuffmanTables, or + * false if setOptimizeHuffmanTables + * has never been called. + * + * @return true if the writer will generate optimized + * Huffman tables. + * + * @see #setOptimizeHuffmanTables + */ + public boolean getOptimizeHuffmanTables() { + return optimizeHuffman; + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGQTable.java b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGQTable.java new file mode 100644 index 000000000..97b975b21 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/JPEGQTable.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2007, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.plugins.jpeg; + +import java.util.Arrays; + +/** + * A class encapsulating a single JPEG quantization table. + * The elements appear in natural order (as opposed to zig-zag order). + * Static variables are provided for the "standard" tables taken from + * Annex K of the JPEG specification, as well as the default tables + * conventionally used for visually lossless encoding. + *

+ * For more information about the operation of the standard JPEG plug-in, + * see the JPEG + * metadata format specification and usage notes + */ + +public class JPEGQTable { + + private static final int[] k1 = { + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, + 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, + 72, 92, 95, 98, 112, 100, 103, 99, + }; + + private static final int[] k1div2 = { + 8, 6, 5, 8, 12, 20, 26, 31, + 6, 6, 7, 10, 13, 29, 30, 28, + 7, 7, 8, 12, 20, 29, 35, 28, + 7, 9, 11, 15, 26, 44, 40, 31, + 9, 11, 19, 28, 34, 55, 52, 39, + 12, 18, 28, 32, 41, 52, 57, 46, + 25, 32, 39, 44, 52, 61, 60, 51, + 36, 46, 48, 49, 56, 50, 52, 50, + }; + + private static final int[] k2 = { + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + }; + + private static final int[] k2div2 = { + 9, 9, 12, 24, 50, 50, 50, 50, + 9, 11, 13, 33, 50, 50, 50, 50, + 12, 13, 28, 50, 50, 50, 50, 50, + 24, 33, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, + }; + + /** + * The sample luminance quantization table given in the JPEG + * specification, table K.1. According to the specification, + * these values produce "good" quality output. + * @see #K1Div2Luminance + */ + public static final JPEGQTable + K1Luminance = new JPEGQTable(k1, false); + + /** + * The sample luminance quantization table given in the JPEG + * specification, table K.1, with all elements divided by 2. + * According to the specification, these values produce "very good" + * quality output. This is the table usually used for "visually lossless" + * encoding, and is the default luminance table used if the default + * tables and quality settings are used. + * @see #K1Luminance + */ + public static final JPEGQTable + K1Div2Luminance = new JPEGQTable(k1div2, false); + + /** + * The sample chrominance quantization table given in the JPEG + * specification, table K.2. According to the specification, + * these values produce "good" quality output. + * @see #K2Div2Chrominance + */ + public static final JPEGQTable K2Chrominance = + new JPEGQTable(k2, false); + + /** + * The sample chrominance quantization table given in the JPEG + * specification, table K.1, with all elements divided by 2. + * According to the specification, these values produce "very good" + * quality output. This is the table usually used for "visually lossless" + * encoding, and is the default chrominance table used if the default + * tables and quality settings are used. + * @see #K2Chrominance + */ + public static final JPEGQTable K2Div2Chrominance = + new JPEGQTable(k2div2, false); + + private int[] qTable; + + private JPEGQTable(int[] table, boolean copy) { + qTable = (copy) ? Arrays.copyOf(table, table.length) : table; + } + + /** + * Constructs a quantization table from the argument, which must + * contain 64 elements in natural order (not zig-zag order). + * A copy is made of the the input array. + * @param table the quantization table, as an int array. + * @throws IllegalArgumentException if table is + * null or table.length is not equal to 64. + */ + public JPEGQTable(int[] table) { + if (table == null) { + throw new IllegalArgumentException("table must not be null."); + } + if (table.length != 64) { + throw new IllegalArgumentException("table.length != 64"); + } + qTable = Arrays.copyOf(table, table.length); + } + + /** + * Returns a copy of the current quantization table as an array + * of {@code int}s in natural (not zig-zag) order. + * @return A copy of the current quantization table. + */ + public int[] getTable() { + return Arrays.copyOf(qTable, qTable.length); + } + + /** + * Returns a new quantization table where the values are multiplied + * by scaleFactor and then clamped to the range 1..32767 + * (or to 1..255 if forceBaseline is true). + *

+ * Values of scaleFactor less than 1 tend to improve + * the quality level of the table, and values greater than 1.0 + * degrade the quality level of the table. + * @param scaleFactor multiplication factor for the table. + * @param forceBaseline if true, + * the values will be clamped to the range 1..255 + * @return a new quantization table that is a linear multiple + * of the current table. + */ + public JPEGQTable getScaledInstance(float scaleFactor, + boolean forceBaseline) { + int max = (forceBaseline) ? 255 : 32767; + int[] scaledTable = new int[qTable.length]; + for (int i=0; i max) { + sv = max; + } + scaledTable[i] = sv; + } + return new JPEGQTable(scaledTable); + } + + /** + * Returns a {@code String} representing this quantization table. + * @return a {@code String} representing this quantization table. + */ + public String toString() { + String ls = System.getProperty("line.separator", "\n"); + StringBuilder sb = new StringBuilder("JPEGQTable:"+ls); + for (int i=0; i < qTable.length; i++) { + if (i % 8 == 0) { + sb.append('\t'); + } + sb.append(qTable[i]); + sb.append(((i % 8) == 7) ? ls : ' '); + } + return sb.toString(); + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/package.html b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/package.html new file mode 100644 index 000000000..60d4e8eea --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/plugins/jpeg/package.html @@ -0,0 +1,69 @@ + + + + + + + + + +Classes supporting the built-in JPEG plug-in. + +

+ +This package contains some support classes for the built-in JPEG +reader and writer plug-ins. Classes are provided for representing +quantization and Huffman tables, and extensions of +ImageReadParam and ImageWriteParam are +provided to supply tables during the reading and writing process. For +more information about the operation of the built-in JPEG plug-ins, +see the JPEG +metadata format specification and usage notes. + +
+
+
+ + + +@since 1.4 + + diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileCacheImageInputStream.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileCacheImageInputStream.java new file mode 100644 index 000000000..b581de9d0 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileCacheImageInputStream.java @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2000, 2012, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.stream; + +import java.io.File; +import java.io.InputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import com.sun.imageio.stream.StreamCloser; +import com.sun.imageio.stream.StreamFinalizer; +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; + +/** + * An implementation of ImageInputStream that gets its + * input from a regular InputStream. A file is used to + * cache previously read data. + * + */ +public class FileCacheImageInputStream extends ImageInputStreamImpl { + + private InputStream stream; + + private File cacheFile; + + private RandomAccessFile cache; + + private static final int BUFFER_LENGTH = 1024; + + private byte[] buf = new byte[BUFFER_LENGTH]; + + private long length = 0L; + + private boolean foundEOF = false; + + /** The referent to be registered with the Disposer. */ + private final Object disposerReferent; + + /** The DisposerRecord that closes the underlying cache. */ + private final DisposerRecord disposerRecord; + + /** The CloseAction that closes the stream in + * the StreamCloser's shutdown hook */ + private final StreamCloser.CloseAction closeAction; + + /** + * Constructs a FileCacheImageInputStream that will read + * from a given InputStream. + * + *

A temporary file is used as a cache. If + * cacheDiris non-null and is a + * directory, the file will be created there. If it is + * null, the system-dependent default temporary-file + * directory will be used (see the documentation for + * File.createTempFile for details). + * + * @param stream an InputStream to read from. + * @param cacheDir a File indicating where the + * cache file should be created, or null to use the + * system directory. + * + * @exception IllegalArgumentException if stream is + * null. + * @exception IllegalArgumentException if cacheDir is + * non-null but is not a directory. + * @exception IOException if a cache file cannot be created. + */ + public FileCacheImageInputStream(InputStream stream, File cacheDir) + throws IOException { + if (stream == null) { + throw new IllegalArgumentException("stream == null!"); + } + if ((cacheDir != null) && !(cacheDir.isDirectory())) { + throw new IllegalArgumentException("Not a directory!"); + } + this.stream = stream; + if (cacheDir == null) + this.cacheFile = Files.createTempFile("imageio", ".tmp").toFile(); + else + this.cacheFile = Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp") + .toFile(); + this.cache = new RandomAccessFile(cacheFile, "rw"); + + this.closeAction = StreamCloser.createCloseAction(this); + StreamCloser.addToQueue(closeAction); + + disposerRecord = new StreamDisposerRecord(cacheFile, cache); + if (getClass() == FileCacheImageInputStream.class) { + disposerReferent = new Object(); + Disposer.addRecord(disposerReferent, disposerRecord); + } else { + disposerReferent = new StreamFinalizer(this); + } + } + + /** + * Ensures that at least pos bytes are cached, + * or the end of the source is reached. The return value + * is equal to the smaller of pos and the + * length of the source file. + */ + private long readUntil(long pos) throws IOException { + // We've already got enough data cached + if (pos < length) { + return pos; + } + // pos >= length but length isn't getting any bigger, so return it + if (foundEOF) { + return length; + } + + long len = pos - length; + cache.seek(length); + while (len > 0) { + // Copy a buffer's worth of data from the source to the cache + // BUFFER_LENGTH will always fit into an int so this is safe + int nbytes = + stream.read(buf, 0, (int)Math.min(len, (long)BUFFER_LENGTH)); + if (nbytes == -1) { + foundEOF = true; + return length; + } + + cache.write(buf, 0, nbytes); + len -= nbytes; + length += nbytes; + } + + return pos; + } + + public int read() throws IOException { + checkClosed(); + bitOffset = 0; + long next = streamPos + 1; + long pos = readUntil(next); + if (pos >= next) { + cache.seek(streamPos++); + return cache.read(); + } else { + return -1; + } + } + + public int read(byte[] b, int off, int len) throws IOException { + checkClosed(); + + if (b == null) { + throw new NullPointerException("b == null!"); + } + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > b.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off+len > b.length || off+len < 0!"); + } + + bitOffset = 0; + + if (len == 0) { + return 0; + } + + long pos = readUntil(streamPos + len); + + // len will always fit into an int so this is safe + len = (int)Math.min((long)len, pos - streamPos); + if (len > 0) { + cache.seek(streamPos); + cache.readFully(b, off, len); + streamPos += len; + return len; + } else { + return -1; + } + } + + /** + * Returns true since this + * ImageInputStream caches data in order to allow + * seeking backwards. + * + * @return true. + * + * @see #isCachedMemory + * @see #isCachedFile + */ + public boolean isCached() { + return true; + } + + /** + * Returns true since this + * ImageInputStream maintains a file cache. + * + * @return true. + * + * @see #isCached + * @see #isCachedMemory + */ + public boolean isCachedFile() { + return true; + } + + /** + * Returns false since this + * ImageInputStream does not maintain a main memory + * cache. + * + * @return false. + * + * @see #isCached + * @see #isCachedFile + */ + public boolean isCachedMemory() { + return false; + } + + /** + * Closes this FileCacheImageInputStream, closing + * and removing the cache file. The source InputStream + * is not closed. + * + * @exception IOException if an error occurs. + */ + public void close() throws IOException { + super.close(); + disposerRecord.dispose(); // this will close/delete the cache file + stream = null; + cache = null; + cacheFile = null; + StreamCloser.removeFromQueue(closeAction); + } + + /** + * {@inheritDoc} + */ + protected void finalize() throws Throwable { + // Empty finalizer: for performance reasons we instead use the + // Disposer mechanism for ensuring that the underlying + // RandomAccessFile is closed/deleted prior to garbage collection + } + + private static class StreamDisposerRecord implements DisposerRecord { + private File cacheFile; + private RandomAccessFile cache; + + public StreamDisposerRecord(File cacheFile, RandomAccessFile cache) { + this.cacheFile = cacheFile; + this.cache = cache; + } + + public synchronized void dispose() { + if (cache != null) { + try { + cache.close(); + } catch (IOException e) { + } finally { + cache = null; + } + } + if (cacheFile != null) { + cacheFile.delete(); + cacheFile = null; + } + // Note: Explicit removal of the stream from the StreamCloser + // queue is not mandatory in this case, as it will be removed + // automatically by GC shortly after this method is called. + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileCacheImageOutputStream.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileCacheImageOutputStream.java new file mode 100644 index 000000000..c5379b165 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileCacheImageOutputStream.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2000, 2012, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.stream; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import com.sun.imageio.stream.StreamCloser; + +/** + * An implementation of ImageOutputStream that writes its + * output to a regular OutputStream. A file is used to + * cache data until it is flushed to the output stream. + * + */ +public class FileCacheImageOutputStream extends ImageOutputStreamImpl { + + private OutputStream stream; + + private File cacheFile; + + private RandomAccessFile cache; + + // Pos after last (rightmost) byte written + private long maxStreamPos = 0L; + + /** The CloseAction that closes the stream in + * the StreamCloser's shutdown hook */ + private final StreamCloser.CloseAction closeAction; + + /** + * Constructs a FileCacheImageOutputStream that will write + * to a given outputStream. + * + *

A temporary file is used as a cache. If + * cacheDiris non-null and is a + * directory, the file will be created there. If it is + * null, the system-dependent default temporary-file + * directory will be used (see the documentation for + * File.createTempFile for details). + * + * @param stream an OutputStream to write to. + * @param cacheDir a File indicating where the + * cache file should be created, or null to use the + * system directory. + * + * @exception IllegalArgumentException if stream + * is null. + * @exception IllegalArgumentException if cacheDir is + * non-null but is not a directory. + * @exception IOException if a cache file cannot be created. + */ + public FileCacheImageOutputStream(OutputStream stream, File cacheDir) + throws IOException { + if (stream == null) { + throw new IllegalArgumentException("stream == null!"); + } + if ((cacheDir != null) && !(cacheDir.isDirectory())) { + throw new IllegalArgumentException("Not a directory!"); + } + this.stream = stream; + if (cacheDir == null) + this.cacheFile = Files.createTempFile("imageio", ".tmp").toFile(); + else + this.cacheFile = Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp") + .toFile(); + this.cache = new RandomAccessFile(cacheFile, "rw"); + + this.closeAction = StreamCloser.createCloseAction(this); + StreamCloser.addToQueue(closeAction); + } + + public int read() throws IOException { + checkClosed(); + bitOffset = 0; + int val = cache.read(); + if (val != -1) { + ++streamPos; + } + return val; + } + + public int read(byte[] b, int off, int len) throws IOException { + checkClosed(); + + if (b == null) { + throw new NullPointerException("b == null!"); + } + if (off < 0 || len < 0 || off + len > b.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off+len > b.length || off+len < 0!"); + } + + bitOffset = 0; + + if (len == 0) { + return 0; + } + + int nbytes = cache.read(b, off, len); + if (nbytes != -1) { + streamPos += nbytes; + } + return nbytes; + } + + public void write(int b) throws IOException { + flushBits(); // this will call checkClosed() for us + cache.write(b); + ++streamPos; + maxStreamPos = Math.max(maxStreamPos, streamPos); + } + + public void write(byte[] b, int off, int len) throws IOException { + flushBits(); // this will call checkClosed() for us + cache.write(b, off, len); + streamPos += len; + maxStreamPos = Math.max(maxStreamPos, streamPos); + } + + public long length() { + try { + checkClosed(); + return cache.length(); + } catch (IOException e) { + return -1L; + } + } + + /** + * Sets the current stream position and resets the bit offset to + * 0. It is legal to seek past the end of the file; an + * EOFException will be thrown only if a read is + * performed. The file length will not be increased until a write + * is performed. + * + * @exception IndexOutOfBoundsException if pos is smaller + * than the flushed position. + * @exception IOException if any other I/O error occurs. + */ + public void seek(long pos) throws IOException { + checkClosed(); + + if (pos < flushedPos) { + throw new IndexOutOfBoundsException(); + } + + cache.seek(pos); + this.streamPos = cache.getFilePointer(); + maxStreamPos = Math.max(maxStreamPos, streamPos); + this.bitOffset = 0; + } + + /** + * Returns true since this + * ImageOutputStream caches data in order to allow + * seeking backwards. + * + * @return true. + * + * @see #isCachedMemory + * @see #isCachedFile + */ + public boolean isCached() { + return true; + } + + /** + * Returns true since this + * ImageOutputStream maintains a file cache. + * + * @return true. + * + * @see #isCached + * @see #isCachedMemory + */ + public boolean isCachedFile() { + return true; + } + + /** + * Returns false since this + * ImageOutputStream does not maintain a main memory + * cache. + * + * @return false. + * + * @see #isCached + * @see #isCachedFile + */ + public boolean isCachedMemory() { + return false; + } + + /** + * Closes this FileCacheImageOutputStream. All + * pending data is flushed to the output, and the cache file + * is closed and removed. The destination OutputStream + * is not closed. + * + * @exception IOException if an error occurs. + */ + public void close() throws IOException { + maxStreamPos = cache.length(); + + seek(maxStreamPos); + flushBefore(maxStreamPos); + super.close(); + cache.close(); + cache = null; + cacheFile.delete(); + cacheFile = null; + stream.flush(); + stream = null; + StreamCloser.removeFromQueue(closeAction); + } + + public void flushBefore(long pos) throws IOException { + long oFlushedPos = flushedPos; + super.flushBefore(pos); // this will call checkClosed() for us + + long flushBytes = flushedPos - oFlushedPos; + if (flushBytes > 0) { + int bufLen = 512; + byte[] buf = new byte[bufLen]; + cache.seek(oFlushedPos); + while (flushBytes > 0) { + int len = (int)Math.min(flushBytes, bufLen); + cache.readFully(buf, 0, len); + stream.write(buf, 0, len); + flushBytes -= len; + } + stream.flush(); + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileImageInputStream.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileImageInputStream.java new file mode 100644 index 000000000..b4c2cf29a --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileImageInputStream.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2000, 2007, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.stream; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import com.sun.imageio.stream.CloseableDisposerRecord; +import com.sun.imageio.stream.StreamFinalizer; +import sun.java2d.Disposer; + +/** + * An implementation of ImageInputStream that gets its + * input from a File or RandomAccessFile. + * The file contents are assumed to be stable during the lifetime of + * the object. + * + */ +public class FileImageInputStream extends ImageInputStreamImpl { + + private RandomAccessFile raf; + + /** The referent to be registered with the Disposer. */ + private final Object disposerReferent; + + /** The DisposerRecord that closes the underlying RandomAccessFile. */ + private final CloseableDisposerRecord disposerRecord; + + /** + * Constructs a FileImageInputStream that will read + * from a given File. + * + *

The file contents must not change between the time this + * object is constructed and the time of the last call to a read + * method. + * + * @param f a File to read from. + * + * @exception IllegalArgumentException if f is + * null. + * @exception SecurityException if a security manager exists + * and does not allow read access to the file. + * @exception FileNotFoundException if f is a + * directory or cannot be opened for reading for any other reason. + * @exception IOException if an I/O error occurs. + */ + public FileImageInputStream(File f) + throws FileNotFoundException, IOException { + this(f == null ? null : new RandomAccessFile(f, "r")); + } + + /** + * Constructs a FileImageInputStream that will read + * from a given RandomAccessFile. + * + *

The file contents must not change between the time this + * object is constructed and the time of the last call to a read + * method. + * + * @param raf a RandomAccessFile to read from. + * + * @exception IllegalArgumentException if raf is + * null. + */ + public FileImageInputStream(RandomAccessFile raf) { + if (raf == null) { + throw new IllegalArgumentException("raf == null!"); + } + this.raf = raf; + + disposerRecord = new CloseableDisposerRecord(raf); + if (getClass() == FileImageInputStream.class) { + disposerReferent = new Object(); + Disposer.addRecord(disposerReferent, disposerRecord); + } else { + disposerReferent = new StreamFinalizer(this); + } + } + + public int read() throws IOException { + checkClosed(); + bitOffset = 0; + int val = raf.read(); + if (val != -1) { + ++streamPos; + } + return val; + } + + public int read(byte[] b, int off, int len) throws IOException { + checkClosed(); + bitOffset = 0; + int nbytes = raf.read(b, off, len); + if (nbytes != -1) { + streamPos += nbytes; + } + return nbytes; + } + + /** + * Returns the length of the underlying file, or -1 + * if it is unknown. + * + * @return the file length as a long, or + * -1. + */ + public long length() { + try { + checkClosed(); + return raf.length(); + } catch (IOException e) { + return -1L; + } + } + + public void seek(long pos) throws IOException { + checkClosed(); + if (pos < flushedPos) { + throw new IndexOutOfBoundsException("pos < flushedPos!"); + } + bitOffset = 0; + raf.seek(pos); + streamPos = raf.getFilePointer(); + } + + public void close() throws IOException { + super.close(); + disposerRecord.dispose(); // this closes the RandomAccessFile + raf = null; + } + + /** + * {@inheritDoc} + */ + protected void finalize() throws Throwable { + // Empty finalizer: for performance reasons we instead use the + // Disposer mechanism for ensuring that the underlying + // RandomAccessFile is closed prior to garbage collection + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileImageOutputStream.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileImageOutputStream.java index b80238146..72b9ab8eb 100644 --- a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileImageOutputStream.java +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/FileImageOutputStream.java @@ -1,17 +1,172 @@ +/* + * Copyright (c) 2000, 2007, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package javax.imageio.stream; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import com.sun.imageio.stream.CloseableDisposerRecord; +import com.sun.imageio.stream.StreamFinalizer; +import sun.java2d.Disposer; /** - *SwingJS placeholder only + * An implementation of ImageOutputStream that writes its + * output directly to a File or + * RandomAccessFile. * */ -public class FileImageOutputStream extends FileOutputStream { +public class FileImageOutputStream extends ImageOutputStreamImpl { + + private RandomAccessFile raf; + + /** The referent to be registered with the Disposer. */ + private final Object disposerReferent; + + /** The DisposerRecord that closes the underlying RandomAccessFile. */ + private final CloseableDisposerRecord disposerRecord; + + /** + * Constructs a FileImageOutputStream that will write + * to a given File. + * + * @param f a File to write to. + * + * @exception IllegalArgumentException if f is + * null. + * @exception SecurityException if a security manager exists + * and does not allow write access to the file. + * @exception FileNotFoundException if f does not denote + * a regular file or it cannot be opened for reading and writing for any + * other reason. + * @exception IOException if an I/O error occurs. + */ + public FileImageOutputStream(File f) + throws FileNotFoundException, IOException { + this(f == null ? null : new RandomAccessFile(f, "rw")); + } + + /** + * Constructs a FileImageOutputStream that will write + * to a given RandomAccessFile. + * + * @param raf a RandomAccessFile to write to. + * + * @exception IllegalArgumentException if raf is + * null. + */ + public FileImageOutputStream(RandomAccessFile raf) { + if (raf == null) { + throw new IllegalArgumentException("raf == null!"); + } + this.raf = raf; + + disposerRecord = new CloseableDisposerRecord(raf); + if (getClass() == FileImageOutputStream.class) { + disposerReferent = new Object(); + Disposer.addRecord(disposerReferent, disposerRecord); + } else { + disposerReferent = new StreamFinalizer(this); + } + } + + public int read() throws IOException { + checkClosed(); + bitOffset = 0; + int val = raf.read(); + if (val != -1) { + ++streamPos; + } + return val; + } + + public int read(byte[] b, int off, int len) throws IOException { + checkClosed(); + bitOffset = 0; + int nbytes = raf.read(b, off, len); + if (nbytes != -1) { + streamPos += nbytes; + } + return nbytes; + } + + public void write(int b) throws IOException { + flushBits(); // this will call checkClosed() for us + raf.write(b); + ++streamPos; + } + + public void write(byte[] b, int off, int len) throws IOException { + flushBits(); // this will call checkClosed() for us + raf.write(b, off, len); + streamPos += len; + } + + public long length() { + try { + checkClosed(); + return raf.length(); + } catch (IOException e) { + return -1L; + } + } + + /** + * Sets the current stream position and resets the bit offset to + * 0. It is legal to seeking past the end of the file; an + * EOFException will be thrown only if a read is + * performed. The file length will not be increased until a write + * is performed. + * + * @exception IndexOutOfBoundsException if pos is smaller + * than the flushed position. + * @exception IOException if any other I/O error occurs. + */ + public void seek(long pos) throws IOException { + checkClosed(); + if (pos < flushedPos) { + throw new IndexOutOfBoundsException("pos < flushedPos!"); + } + bitOffset = 0; + raf.seek(pos); + streamPos = raf.getFilePointer(); + } + + public void close() throws IOException { + super.close(); + disposerRecord.dispose(); // this closes the RandomAccessFile + raf = null; + } - public FileImageOutputStream(File file) throws FileNotFoundException { - super(file); - } - + /** + * {@inheritDoc} + */ + protected void finalize() throws Throwable { + // Empty finalizer: for performance reasons we instead use the + // Disposer mechanism for ensuring that the underlying + // RandomAccessFile is closed prior to garbage collection + } } diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageInputStream.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageInputStream.java index dc6ce8c9f..9a5b04240 100644 --- a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageInputStream.java +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageInputStream.java @@ -1,9 +1,1000 @@ +/* + * Copyright (c) 1999, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package javax.imageio.stream; +import java.io.Closeable; +import java.io.DataInput; +import java.io.IOException; +import java.nio.ByteOrder; + +/** + * A seekable input stream interface for use by + * ImageReaders. Various input sources, such as + * InputStreams and Files, + * as well as future fast I/O sources may be "wrapped" by a suitable + * implementation of this interface for use by the Image I/O API. + * + * @see ImageInputStreamImpl + * @see FileImageInputStream + * @see FileCacheImageInputStream + * @see MemoryCacheImageInputStream + * + */ +public interface ImageInputStream extends DataInput, Closeable { + + /** + * Sets the desired byte order for future reads of data values + * from this stream. For example, the sequence of bytes '0x01 + * 0x02 0x03 0x04' if read as a 4-byte integer would have the + * value '0x01020304' using network byte order and the value + * '0x04030201' under the reverse byte order. + * + *

The enumeration class java.nio.ByteOrder is + * used to specify the byte order. A value of + * ByteOrder.BIG_ENDIAN specifies so-called + * big-endian or network byte order, in which the high-order byte + * comes first. Motorola and Sparc processors store data in this + * format, while Intel processors store data in the reverse + * ByteOrder.LITTLE_ENDIAN order. + * + *

The byte order has no effect on the results returned from + * the readBits method (or the value written by + * ImageOutputStream.writeBits). + * + * @param byteOrder one of ByteOrder.BIG_ENDIAN or + * java.nio.ByteOrder.LITTLE_ENDIAN, indicating whether + * network byte order or its reverse will be used for future + * reads. + * + * @see java.nio.ByteOrder + * @see #getByteOrder + * @see #readBits(int) + */ + void setByteOrder(ByteOrder byteOrder); + + /** + * Returns the byte order with which data values will be read from + * this stream as an instance of the + * java.nio.ByteOrder enumeration. + * + * @return one of ByteOrder.BIG_ENDIAN or + * ByteOrder.LITTLE_ENDIAN, indicating which byte + * order is being used. + * + * @see java.nio.ByteOrder + * @see #setByteOrder + */ + ByteOrder getByteOrder(); + + /** + * Reads a single byte from the stream and returns it as an + * integer between 0 and 255. If the end of the stream is + * reached, -1 is returned. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return a byte value from the stream, as an int, or -1 to + * indicate EOF. + * + * @exception IOException if an I/O error occurs. + */ + int read() throws IOException; + + /** + * Reads up to b.length bytes from the stream, and + * stores them into b starting at index 0. The + * number of bytes read is returned. If no bytes can be read + * because the end of the stream has been reached, -1 is returned. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param b an array of bytes to be written to. + * + * @return the number of bytes actually read, or -1 + * to indicate EOF. + * + * @exception NullPointerException if b is + * null. + * + * @exception IOException if an I/O error occurs. + */ + int read(byte[] b) throws IOException; + + /** + * Reads up to len bytes from the stream, and stores + * them into b starting at index off. + * The number of bytes read is returned. If no bytes can be read + * because the end of the stream has been reached, -1 + * is returned. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param b an array of bytes to be written to. + * @param off the starting position within b to write to. + * @param len the maximum number of bytes to read. + * + * @return the number of bytes actually read, or -1 + * to indicate EOF. + * + * @exception NullPointerException if b is + * null. + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than b.length. + * @exception IOException if an I/O error occurs. + */ + int read(byte[] b, int off, int len) throws IOException; + + /** + * Reads up to len bytes from the stream, and + * modifies the supplied IIOByteBuffer to indicate + * the byte array, offset, and length where the data may be found. + * The caller should not attempt to modify the data found in the + * IIOByteBuffer. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param buf an IIOByteBuffer object to be modified. + * @param len the maximum number of bytes to read. + * + * @exception IndexOutOfBoundsException if len is + * negative. + * @exception NullPointerException if buf is + * null. + * + * @exception IOException if an I/O error occurs. + */ + void readBytes(IIOByteBuffer buf, int len) throws IOException; + + /** + * Reads a byte from the stream and returns a boolean + * value of true if it is nonzero, false + * if it is zero. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return a boolean value from the stream. + * + * @exception java.io.EOFException if the end of the stream is reached. + * @exception IOException if an I/O error occurs. + */ + boolean readBoolean() throws IOException; + + /** + * Reads a byte from the stream and returns it as a + * byte value. Byte values between 0x00 + * and 0x7f represent integer values between + * 0 and 127. Values between + * 0x80 and 0xff represent negative + * values from -128 to /1. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return a signed byte value from the stream. + * + * @exception java.io.EOFException if the end of the stream is reached. + * @exception IOException if an I/O error occurs. + */ + byte readByte() throws IOException; + + /** + * Reads a byte from the stream, and (conceptually) converts it to + * an int, masks it with 0xff in order to strip off + * any sign-extension bits, and returns it as a byte + * value. + * + *

Thus, byte values between 0x00 and + * 0x7f are simply returned as integer values between + * 0 and 127. Values between + * 0x80 and 0xff, which normally + * represent negative bytevalues, will be mapped into + * positive integers between 128 and + * 255. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return an unsigned byte value from the stream. + * + * @exception java.io.EOFException if the end of the stream is reached. + * @exception IOException if an I/O error occurs. + */ + int readUnsignedByte() throws IOException; + + /** + * Reads two bytes from the stream, and (conceptually) + * concatenates them according to the current byte order, and + * returns the result as a short value. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return a signed short value from the stream. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * + * @see #getByteOrder + */ + short readShort() throws IOException; + + /** + * Reads two bytes from the stream, and (conceptually) + * concatenates them according to the current byte order, converts + * the resulting value to an int, masks it with + * 0xffff in order to strip off any sign-extension + * buts, and returns the result as an unsigned int + * value. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return an unsigned short value from the stream, as an int. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * + * @see #getByteOrder + */ + int readUnsignedShort() throws IOException; + + /** + * Equivalent to readUnsignedShort, except that the + * result is returned using the char datatype. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return an unsigned char value from the stream. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * + * @see #readUnsignedShort + */ + char readChar() throws IOException; + + /** + * Reads 4 bytes from the stream, and (conceptually) concatenates + * them according to the current byte order and returns the result + * as an int. + * + *

The bit offset within the stream is ignored and treated as + * though it were zero. + * + * @return a signed int value from the stream. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * + * @see #getByteOrder + */ + int readInt() throws IOException; + + /** + * Reads 4 bytes from the stream, and (conceptually) concatenates + * them according to the current byte order, converts the result + * to a long, masks it with 0xffffffffL in order to + * strip off any sign-extension bits, and returns the result as an + * unsigned long value. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return an unsigned int value from the stream, as a long. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * + * @see #getByteOrder + */ + long readUnsignedInt() throws IOException; + + /** + * Reads 8 bytes from the stream, and (conceptually) concatenates + * them according to the current byte order and returns the result + * as a long. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return a signed long value from the stream. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * + * @see #getByteOrder + */ + long readLong() throws IOException; + + /** + * Reads 4 bytes from the stream, and (conceptually) concatenates + * them according to the current byte order and returns the result + * as a float. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return a float value from the stream. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * + * @see #getByteOrder + */ + float readFloat() throws IOException; + + /** + * Reads 8 bytes from the stream, and (conceptually) concatenates + * them according to the current byte order and returns the result + * as a double. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return a double value from the stream. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + * + * @see #getByteOrder + */ + double readDouble() throws IOException; + + /** + * Reads the next line of text from the input stream. It reads + * successive bytes, converting each byte separately into a + * character, until it encounters a line terminator or end of + * file; the characters read are then returned as a + * String. Note that because this method processes + * bytes, it does not support input of the full Unicode character + * set. + * + *

If end of file is encountered before even one byte can be + * read, then null is returned. Otherwise, each byte + * that is read is converted to type char by + * zero-extension. If the character '\n' is + * encountered, it is discarded and reading ceases. If the + * character '\r' is encountered, it is discarded + * and, if the following byte converts to the character + * '\n', then that is discarded also; reading then + * ceases. If end of file is encountered before either of the + * characters '\n' and '\r' is + * encountered, reading ceases. Once reading has ceased, a + * String is returned that contains all the + * characters read and not discarded, taken in order. Note that + * every character in this string will have a value less than + * \u0100, that is, (char)256. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return a String containing a line of text from the stream. + * + * @exception IOException if an I/O error occurs. + */ + String readLine() throws IOException; + + /** + * Reads in a string that has been encoded using a + * modified + * UTF-8 + * format. The general contract of readUTF is that + * it reads a representation of a Unicode character string encoded + * in modified UTF-8 format; this string of characters is + * then returned as a String. + * + *

First, two bytes are read and used to construct an unsigned + * 16-bit integer in the manner of the + * readUnsignedShort method, using network byte order + * (regardless of the current byte order setting). This integer + * value is called the UTF length and specifies the number + * of additional bytes to be read. These bytes are then converted + * to characters by considering them in groups. The length of each + * group is computed from the value of the first byte of the + * group. The byte following a group, if any, is the first byte of + * the next group. + * + *

If the first byte of a group matches the bit pattern + * 0xxxxxxx (where x means "may be + * 0 or 1"), then the group consists of + * just that byte. The byte is zero-extended to form a character. + * + *

If the first byte of a group matches the bit pattern + * 110xxxxx, then the group consists of that byte + * a and a second byte b. If there is no + * byte b (because byte a was the last + * of the bytes to be read), or if byte b does not + * match the bit pattern 10xxxxxx, then a + * UTFDataFormatException is thrown. Otherwise, the + * group is converted to the character: + * + *


+     * (char)(((a& 0x1F) << 6) | (b & 0x3F))
+     * 
+ * + * If the first byte of a group matches the bit pattern + * 1110xxxx, then the group consists of that byte + * a and two more bytes b and + * c. If there is no byte c (because + * byte a was one of the last two of the bytes to be + * read), or either byte b or byte c + * does not match the bit pattern 10xxxxxx, then a + * UTFDataFormatException is thrown. Otherwise, the + * group is converted to the character: + * + *


+     * (char)(((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F))
+     * 
+ * + * If the first byte of a group matches the pattern + * 1111xxxx or the pattern 10xxxxxx, + * then a UTFDataFormatException is thrown. + * + *

If end of file is encountered at any time during this + * entire process, then an java.io.EOFException is thrown. + * + *

After every group has been converted to a character by this + * process, the characters are gathered, in the same order in + * which their corresponding groups were read from the input + * stream, to form a String, which is returned. + * + *

The current byte order setting is ignored. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + *

Note: This method should not be used in + * the implementation of image formats that use standard UTF-8, + * because the modified UTF-8 used here is incompatible with + * standard UTF-8. + * + * @return a String read from the stream. + * + * @exception java.io.EOFException if this stream reaches the end + * before reading all the bytes. + * @exception java.io.UTFDataFormatException if the bytes do not represent + * a valid modified UTF-8 encoding of a string. + * @exception IOException if an I/O error occurs. + */ + String readUTF() throws IOException; + + /** + * Reads len bytes from the stream, and stores them + * into b starting at index off. + * If the end of the stream is reached, an java.io.EOFException + * will be thrown. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param b an array of bytes to be written to. + * @param off the starting position within b to write to. + * @param len the maximum number of bytes to read. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than b.length. + * @exception NullPointerException if b is + * null. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + */ + void readFully(byte[] b, int off, int len) throws IOException; + + /** + * Reads b.length bytes from the stream, and stores them + * into b starting at index 0. + * If the end of the stream is reached, an java.io.EOFException + * will be thrown. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param b an array of bytes. + * + * @exception NullPointerException if b is + * null. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + */ + void readFully(byte[] b) throws IOException; + + /** + * Reads len shorts (signed 16-bit integers) from the + * stream according to the current byte order, and + * stores them into s starting at index + * off. If the end of the stream is reached, an + * java.io.EOFException will be thrown. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param s an array of shorts to be written to. + * @param off the starting position within s to write to. + * @param len the maximum number of shorts to read. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than s.length. + * @exception NullPointerException if s is + * null. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + */ + void readFully(short[] s, int off, int len) throws IOException; + + /** + * Reads len chars (unsigned 16-bit integers) from the + * stream according to the current byte order, and + * stores them into c starting at index + * off. If the end of the stream is reached, an + * java.io.EOFException will be thrown. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param c an array of chars to be written to. + * @param off the starting position within c to write to. + * @param len the maximum number of chars to read. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than c.length. + * @exception NullPointerException if c is + * null. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + */ + void readFully(char[] c, int off, int len) throws IOException; + + /** + * Reads len ints (signed 32-bit integers) from the + * stream according to the current byte order, and + * stores them into i starting at index + * off. If the end of the stream is reached, an + * java.io.EOFException will be thrown. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param i an array of ints to be written to. + * @param off the starting position within i to write to. + * @param len the maximum number of ints to read. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than i.length. + * @exception NullPointerException if i is + * null. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + */ + void readFully(int[] i, int off, int len) throws IOException; + + /** + * Reads len longs (signed 64-bit integers) from the + * stream according to the current byte order, and + * stores them into l starting at index + * off. If the end of the stream is reached, an + * java.io.EOFException will be thrown. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param l an array of longs to be written to. + * @param off the starting position within l to write to. + * @param len the maximum number of longs to read. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than l.length. + * @exception NullPointerException if l is + * null. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + */ + void readFully(long[] l, int off, int len) throws IOException; + + /** + * Reads len floats (32-bit IEEE single-precision + * floats) from the stream according to the current byte order, + * and stores them into f starting at + * index off. If the end of the stream is reached, + * an java.io.EOFException will be thrown. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param f an array of floats to be written to. + * @param off the starting position within f to write to. + * @param len the maximum number of floats to read. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than f.length. + * @exception NullPointerException if f is + * null. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + */ + void readFully(float[] f, int off, int len) throws IOException; + + /** + * Reads len doubles (64-bit IEEE double-precision + * floats) from the stream according to the current byte order, + * and stores them into d starting at + * index off. If the end of the stream is reached, + * an java.io.EOFException will be thrown. + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @param d an array of doubles to be written to. + * @param off the starting position within d to write to. + * @param len the maximum number of doubles to read. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than d.length. + * @exception NullPointerException if d is + * null. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bytes. + * @exception IOException if an I/O error occurs. + */ + void readFully(double[] d, int off, int len) throws IOException; + + /** + * Returns the current byte position of the stream. The next read + * will take place starting at this offset. + * + * @return a long containing the position of the stream. + * + * @exception IOException if an I/O error occurs. + */ + long getStreamPosition() throws IOException; + + /** + * Returns the current bit offset, as an integer between 0 and 7, + * inclusive. The bit offset is updated implicitly by calls to + * the readBits method. A value of 0 indicates the + * most-significant bit, and a value of 7 indicates the least + * significant bit, of the byte being read. + * + *

The bit offset is set to 0 when a stream is first + * opened, and is reset to 0 by calls to seek, + * skipBytes, or any read or + * readFully method. + * + * @return an int containing the bit offset between + * 0 and 7, inclusive. + * + * @exception IOException if an I/O error occurs. + * + * @see #setBitOffset + */ + int getBitOffset() throws IOException; + + /** + * Sets the bit offset to an integer between 0 and 7, inclusive. + * The byte offset within the stream, as returned by + * getStreamPosition, is left unchanged. + * A value of 0 indicates the + * most-significant bit, and a value of 7 indicates the least + * significant bit, of the byte being read. + * + * @param bitOffset the desired offset, as an int + * between 0 and 7, inclusive. + * + * @exception IllegalArgumentException if bitOffset + * is not between 0 and 7, inclusive. + * @exception IOException if an I/O error occurs. + * + * @see #getBitOffset + */ + void setBitOffset(int bitOffset) throws IOException; + + /** + * Reads a single bit from the stream and returns it as an + * int with the value 0 or + * 1. The bit offset is advanced by one and reduced + * modulo 8. + * + * @return an int containing the value 0 + * or 1. + * + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bits. + * @exception IOException if an I/O error occurs. + */ + int readBit() throws IOException; + + /** + * Reads a bitstring from the stream and returns it as a + * long, with the first bit read becoming the most + * significant bit of the output. The read starts within the byte + * indicated by getStreamPosition, at the bit given + * by getBitOffset. The bit offset is advanced by + * numBits and reduced modulo 8. + * + *

The byte order of the stream has no effect on this + * method. The return value of this method is constructed as + * though the bits were read one at a time, and shifted into + * the right side of the return value, as shown by the following + * pseudo-code: + * + *

{@code
+     * long accum = 0L;
+     * for (int i = 0; i < numBits; i++) {
+     *   accum <<= 1; // Shift left one bit to make room
+     *   accum |= readBit();
+     * }
+     * }
+ * + * Note that the result of readBits(32) may thus not + * be equal to that of readInt() if a reverse network + * byte order is being used (i.e., getByteOrder() == + * false). + * + *

If the end of the stream is encountered before all the bits + * have been read, an java.io.EOFException is thrown. + * + * @param numBits the number of bits to read, as an int + * between 0 and 64, inclusive. + * @return the bitstring, as a long with the last bit + * read stored in the least significant bit. + * + * @exception IllegalArgumentException if numBits + * is not between 0 and 64, inclusive. + * @exception java.io.EOFException if the stream reaches the end before + * reading all the bits. + * @exception IOException if an I/O error occurs. + */ + long readBits(int numBits) throws IOException; + + /** + * Returns the total length of the stream, if known. Otherwise, + * -1 is returned. + * + * @return a long containing the length of the + * stream, if known, or else -1. + * + * @exception IOException if an I/O error occurs. + */ + long length() throws IOException; + + /** + * Moves the stream position forward by a given number of bytes. It + * is possible that this method will only be able to skip forward + * by a smaller number of bytes than requested, for example if the + * end of the stream is reached. In all cases, the actual number + * of bytes skipped is returned. The bit offset is set to zero + * prior to advancing the position. + * + * @param n an int containing the number of bytes to + * be skipped. + * + * @return an int representing the number of bytes skipped. + * + * @exception IOException if an I/O error occurs. + */ + int skipBytes(int n) throws IOException; + + /** + * Moves the stream position forward by a given number of bytes. + * This method is identical to skipBytes(int) except + * that it allows for a larger skip distance. + * + * @param n a long containing the number of bytes to + * be skipped. + * + * @return a long representing the number of bytes + * skipped. + * + * @exception IOException if an I/O error occurs. + */ + long skipBytes(long n) throws IOException; + + /** + * Sets the current stream position to the desired location. The + * next read will occur at this location. The bit offset is set + * to 0. + * + *

An IndexOutOfBoundsException will be thrown if + * pos is smaller than the flushed position (as + * returned by getflushedPosition). + * + *

It is legal to seek past the end of the file; an + * java.io.EOFException will be thrown only if a read is + * performed. + * + * @param pos a long containing the desired file + * pointer position. + * + * @exception IndexOutOfBoundsException if pos is smaller + * than the flushed position. + * @exception IOException if any other I/O error occurs. + */ + void seek(long pos) throws IOException; + + /** + * Marks a position in the stream to be returned to by a + * subsequent call to reset. Unlike a standard + * InputStream, all ImageInputStreams + * support marking. Additionally, calls to mark and + * reset may be nested arbitrarily. + * + *

Unlike the mark methods declared by the + * Reader and InputStream interfaces, no + * readLimit parameter is used. An arbitrary amount + * of data may be read following the call to mark. + * + *

The bit position used by the readBits method + * is saved and restored by each pair of calls to + * mark and reset. + * + *

Note that it is valid for an ImageReader to call + * flushBefore as part of a read operation. + * Therefore, if an application calls mark prior to + * passing that stream to an ImageReader, the application + * should not assume that the marked position will remain valid after + * the read operation has completed. + */ + void mark(); + + /** + * Returns the stream pointer to its previous position, including + * the bit offset, at the time of the most recent unmatched call + * to mark. + * + *

Calls to reset without a corresponding call + * to mark have no effect. + * + *

An IOException will be thrown if the previous + * marked position lies in the discarded portion of the stream. + * + * @exception IOException if an I/O error occurs. + */ + void reset() throws IOException; + + /** + * Discards the initial portion of the stream prior to the + * indicated position. Attempting to seek to an offset within the + * flushed portion of the stream will result in an + * IndexOutOfBoundsException. + * + *

Calling flushBefore may allow classes + * implementing this interface to free up resources such as memory + * or disk space that are being used to store data from the + * stream. + * + * @param pos a long containing the length of the + * stream prefix that may be flushed. + * + * @exception IndexOutOfBoundsException if pos lies + * in the flushed portion of the stream or past the current stream + * position. + * @exception IOException if an I/O error occurs. + */ + void flushBefore(long pos) throws IOException; + + /** + * Discards the initial position of the stream prior to the current + * stream position. Equivalent to + * flushBefore(getStreamPosition()). + * + * @exception IOException if an I/O error occurs. + */ + void flush() throws IOException; + + /** + * Returns the earliest position in the stream to which seeking + * may be performed. The returned value will be the maximum of + * all values passed into previous calls to + * flushBefore. + * + * @return the earliest legal position for seeking, as a + * long. + */ + long getFlushedPosition(); + + /** + * Returns true if this ImageInputStream + * caches data itself in order to allow seeking backwards. + * Applications may consult this in order to decide how frequently, + * or whether, to flush in order to conserve cache resources. + * + * @return true if this ImageInputStream + * caches data. + * + * @see #isCachedMemory + * @see #isCachedFile + */ + boolean isCached(); -// SwingJS placeholder + /** + * Returns true if this ImageInputStream + * caches data itself in order to allow seeking backwards, and + * the cache is kept in main memory. Applications may consult + * this in order to decide how frequently, or whether, to flush + * in order to conserve cache resources. + * + * @return true if this ImageInputStream + * caches data in main memory. + * + * @see #isCached + * @see #isCachedFile + */ + boolean isCachedMemory(); -public interface ImageInputStream { - + /** + * Returns true if this ImageInputStream + * caches data itself in order to allow seeking backwards, and + * the cache is kept in a temporary file. Applications may consult + * this in order to decide how frequently, or whether, to flush + * in order to conserve cache resources. + * + * @return true if this ImageInputStream + * caches data in a temporary file. + * + * @see #isCached + * @see #isCachedMemory + */ + boolean isCachedFile(); + /** + * Closes the stream. Attempts to access a stream that has been + * closed may result in IOExceptions or incorrect + * behavior. Calling this method may allow classes implementing + * this interface to release resources associated with the stream + * such as memory, disk space, or file descriptors. + * + * @exception IOException if an I/O error occurs. + */ + void close() throws IOException; } diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageInputStreamImpl.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageInputStreamImpl.java new file mode 100644 index 000000000..735bab830 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageInputStreamImpl.java @@ -0,0 +1,880 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.stream; + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Stack; +import javax.imageio.IIOException; + +/** + * An abstract class implementing the ImageInputStream interface. + * This class is designed to reduce the number of methods that must + * be implemented by subclasses. + * + *

In particular, this class handles most or all of the details of + * byte order interpretation, buffering, mark/reset, discarding, + * closing, and disposing. + */ +public abstract class ImageInputStreamImpl implements ImageInputStream { + + private Stack markByteStack = new Stack(); + + private Stack markBitStack = new Stack(); + + private boolean isClosed = false; + + // Length of the buffer used for readFully(type[], int, int) + private static final int BYTE_BUF_LENGTH = 8192; + + /** + * Byte buffer used for readFully(type[], int, int). Note that this + * array is also used for bulk reads in readShort(), readInt(), etc, so + * it should be large enough to hold a primitive value (i.e. >= 8 bytes). + * Also note that this array is package protected, so that it can be + * used by ImageOutputStreamImpl in a similar manner. + */ + byte[] byteBuf = new byte[BYTE_BUF_LENGTH]; + + /** + * The byte order of the stream as an instance of the enumeration + * class java.nio.ByteOrder, where + * ByteOrder.BIG_ENDIAN indicates network byte order + * and ByteOrder.LITTLE_ENDIAN indicates the reverse + * order. By default, the value is + * ByteOrder.BIG_ENDIAN. + */ + protected ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; + + /** + * The current read position within the stream. Subclasses are + * responsible for keeping this value current from any method they + * override that alters the read position. + */ + protected long streamPos; + + /** + * The current bit offset within the stream. Subclasses are + * responsible for keeping this value current from any method they + * override that alters the bit offset. + */ + protected int bitOffset; + + /** + * The position prior to which data may be discarded. Seeking + * to a smaller position is not allowed. flushedPos + * will always be {@literal >= 0}. + */ + protected long flushedPos = 0; + + /** + * Constructs an ImageInputStreamImpl. + */ + public ImageInputStreamImpl() { + } + + /** + * Throws an IOException if the stream has been closed. + * Subclasses may call this method from any of their methods that + * require the stream not to be closed. + * + * @exception IOException if the stream is closed. + */ + protected final void checkClosed() throws IOException { + if (isClosed) { + throw new IOException("closed"); + } + } + + public void setByteOrder(ByteOrder byteOrder) { + this.byteOrder = byteOrder; + } + + public ByteOrder getByteOrder() { + return byteOrder; + } + + /** + * Reads a single byte from the stream and returns it as an + * int between 0 and 255. If EOF is reached, + * -1 is returned. + * + *

Subclasses must provide an implementation for this method. + * The subclass implementation should update the stream position + * before exiting. + * + *

The bit offset within the stream must be reset to zero before + * the read occurs. + * + * @return the value of the next byte in the stream, or -1 + * if EOF is reached. + * + * @exception IOException if the stream has been closed. + */ + public abstract int read() throws IOException; + + /** + * A convenience method that calls read(b, 0, b.length). + * + *

The bit offset within the stream is reset to zero before + * the read occurs. + * + * @return the number of bytes actually read, or -1 + * to indicate EOF. + * + * @exception NullPointerException if b is + * null. + * @exception IOException if an I/O error occurs. + */ + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Reads up to len bytes from the stream, and stores + * them into b starting at index off. + * If no bytes can be read because the end of the stream has been + * reached, -1 is returned. + * + *

The bit offset within the stream must be reset to zero before + * the read occurs. + * + *

Subclasses must provide an implementation for this method. + * The subclass implementation should update the stream position + * before exiting. + * + * @param b an array of bytes to be written to. + * @param off the starting position within b to write to. + * @param len the maximum number of bytes to read. + * + * @return the number of bytes actually read, or -1 + * to indicate EOF. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than b.length. + * @exception NullPointerException if b is + * null. + * @exception IOException if an I/O error occurs. + */ + public abstract int read(byte[] b, int off, int len) throws IOException; + + public void readBytes(IIOByteBuffer buf, int len) throws IOException { + if (len < 0) { + throw new IndexOutOfBoundsException("len < 0!"); + } + if (buf == null) { + throw new NullPointerException("buf == null!"); + } + + byte[] data = new byte[len]; + len = read(data, 0, len); + + buf.setData(data); + buf.setOffset(0); + buf.setLength(len); + } + + public boolean readBoolean() throws IOException { + int ch = this.read(); + if (ch < 0) { + throw new EOFException(); + } + return (ch != 0); + } + + public byte readByte() throws IOException { + int ch = this.read(); + if (ch < 0) { + throw new EOFException(); + } + return (byte)ch; + } + + public int readUnsignedByte() throws IOException { + int ch = this.read(); + if (ch < 0) { + throw new EOFException(); + } + return ch; + } + + public short readShort() throws IOException { + if (read(byteBuf, 0, 2) != 2) { + throw new EOFException(); + } + + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return (short) + (((byteBuf[0] & 0xff) << 8) | ((byteBuf[1] & 0xff) << 0)); + } else { + return (short) + (((byteBuf[1] & 0xff) << 8) | ((byteBuf[0] & 0xff) << 0)); + } + } + + public int readUnsignedShort() throws IOException { + return ((int)readShort()) & 0xffff; + } + + public char readChar() throws IOException { + return (char)readShort(); + } + + public int readInt() throws IOException { + if (read(byteBuf, 0, 4) != 4) { + throw new EOFException(); + } + + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return + (((byteBuf[0] & 0xff) << 24) | ((byteBuf[1] & 0xff) << 16) | + ((byteBuf[2] & 0xff) << 8) | ((byteBuf[3] & 0xff) << 0)); + } else { + return + (((byteBuf[3] & 0xff) << 24) | ((byteBuf[2] & 0xff) << 16) | + ((byteBuf[1] & 0xff) << 8) | ((byteBuf[0] & 0xff) << 0)); + } + } + + public long readUnsignedInt() throws IOException { + return ((long)readInt()) & 0xffffffffL; + } + + public long readLong() throws IOException { + // REMIND: Once 6277756 is fixed, we should do a bulk read of all 8 + // bytes here as we do in readShort() and readInt() for even better + // performance (see 6347575 for details). + int i1 = readInt(); + int i2 = readInt(); + + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return ((long)i1 << 32) + (i2 & 0xFFFFFFFFL); + } else { + return ((long)i2 << 32) + (i1 & 0xFFFFFFFFL); + } + } + + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + public String readLine() throws IOException { + StringBuffer input = new StringBuffer(); + int c = -1; + boolean eol = false; + + while (!eol) { + switch (c = read()) { + case -1: + case '\n': + eol = true; + break; + case '\r': + eol = true; + long cur = getStreamPosition(); + if ((read()) != '\n') { + seek(cur); + } + break; + default: + input.append((char)c); + break; + } + } + + if ((c == -1) && (input.length() == 0)) { + return null; + } + return input.toString(); + } + + public String readUTF() throws IOException { + this.bitOffset = 0; + + // Fix 4494369: method ImageInputStreamImpl.readUTF() + // does not work as specified (it should always assume + // network byte order). + ByteOrder oldByteOrder = getByteOrder(); + setByteOrder(ByteOrder.BIG_ENDIAN); + + String ret; + try { + ret = DataInputStream.readUTF(this); + } catch (IOException e) { + // Restore the old byte order even if an exception occurs + setByteOrder(oldByteOrder); + throw e; + } + + setByteOrder(oldByteOrder); + return ret; + } + + public void readFully(byte[] b, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > b.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > b.length!"); + } + + while (len > 0) { + int nbytes = read(b, off, len); + if (nbytes == -1) { + throw new EOFException(); + } + off += nbytes; + len -= nbytes; + } + } + + public void readFully(byte[] b) throws IOException { + readFully(b, 0, b.length); + } + + public void readFully(short[] s, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > s.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > s.length!"); + } + + while (len > 0) { + int nelts = Math.min(len, byteBuf.length/2); + readFully(byteBuf, 0, nelts*2); + toShorts(byteBuf, s, off, nelts); + off += nelts; + len -= nelts; + } + } + + public void readFully(char[] c, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > c.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > c.length!"); + } + + while (len > 0) { + int nelts = Math.min(len, byteBuf.length/2); + readFully(byteBuf, 0, nelts*2); + toChars(byteBuf, c, off, nelts); + off += nelts; + len -= nelts; + } + } + + public void readFully(int[] i, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > i.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > i.length!"); + } + + while (len > 0) { + int nelts = Math.min(len, byteBuf.length/4); + readFully(byteBuf, 0, nelts*4); + toInts(byteBuf, i, off, nelts); + off += nelts; + len -= nelts; + } + } + + public void readFully(long[] l, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > l.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > l.length!"); + } + + while (len > 0) { + int nelts = Math.min(len, byteBuf.length/8); + readFully(byteBuf, 0, nelts*8); + toLongs(byteBuf, l, off, nelts); + off += nelts; + len -= nelts; + } + } + + public void readFully(float[] f, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > f.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > f.length!"); + } + + while (len > 0) { + int nelts = Math.min(len, byteBuf.length/4); + readFully(byteBuf, 0, nelts*4); + toFloats(byteBuf, f, off, nelts); + off += nelts; + len -= nelts; + } + } + + public void readFully(double[] d, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > d.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > d.length!"); + } + + while (len > 0) { + int nelts = Math.min(len, byteBuf.length/8); + readFully(byteBuf, 0, nelts*8); + toDoubles(byteBuf, d, off, nelts); + off += nelts; + len -= nelts; + } + } + + private void toShorts(byte[] b, short[] s, int off, int len) { + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < len; j++) { + int b0 = b[boff]; + int b1 = b[boff + 1] & 0xff; + s[off + j] = (short)((b0 << 8) | b1); + boff += 2; + } + } else { + for (int j = 0; j < len; j++) { + int b0 = b[boff + 1]; + int b1 = b[boff] & 0xff; + s[off + j] = (short)((b0 << 8) | b1); + boff += 2; + } + } + } + + private void toChars(byte[] b, char[] c, int off, int len) { + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < len; j++) { + int b0 = b[boff]; + int b1 = b[boff + 1] & 0xff; + c[off + j] = (char)((b0 << 8) | b1); + boff += 2; + } + } else { + for (int j = 0; j < len; j++) { + int b0 = b[boff + 1]; + int b1 = b[boff] & 0xff; + c[off + j] = (char)((b0 << 8) | b1); + boff += 2; + } + } + } + + private void toInts(byte[] b, int[] i, int off, int len) { + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < len; j++) { + int b0 = b[boff]; + int b1 = b[boff + 1] & 0xff; + int b2 = b[boff + 2] & 0xff; + int b3 = b[boff + 3] & 0xff; + i[off + j] = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + boff += 4; + } + } else { + for (int j = 0; j < len; j++) { + int b0 = b[boff + 3]; + int b1 = b[boff + 2] & 0xff; + int b2 = b[boff + 1] & 0xff; + int b3 = b[boff] & 0xff; + i[off + j] = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + boff += 4; + } + } + } + + private void toLongs(byte[] b, long[] l, int off, int len) { + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < len; j++) { + int b0 = b[boff]; + int b1 = b[boff + 1] & 0xff; + int b2 = b[boff + 2] & 0xff; + int b3 = b[boff + 3] & 0xff; + int b4 = b[boff + 4]; + int b5 = b[boff + 5] & 0xff; + int b6 = b[boff + 6] & 0xff; + int b7 = b[boff + 7] & 0xff; + + int i0 = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + int i1 = (b4 << 24) | (b5 << 16) | (b6 << 8) | b7; + + l[off + j] = ((long)i0 << 32) | (i1 & 0xffffffffL); + boff += 8; + } + } else { + for (int j = 0; j < len; j++) { + int b0 = b[boff + 7]; + int b1 = b[boff + 6] & 0xff; + int b2 = b[boff + 5] & 0xff; + int b3 = b[boff + 4] & 0xff; + int b4 = b[boff + 3]; + int b5 = b[boff + 2] & 0xff; + int b6 = b[boff + 1] & 0xff; + int b7 = b[boff] & 0xff; + + int i0 = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + int i1 = (b4 << 24) | (b5 << 16) | (b6 << 8) | b7; + + l[off + j] = ((long)i0 << 32) | (i1 & 0xffffffffL); + boff += 8; + } + } + } + + private void toFloats(byte[] b, float[] f, int off, int len) { + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < len; j++) { + int b0 = b[boff]; + int b1 = b[boff + 1] & 0xff; + int b2 = b[boff + 2] & 0xff; + int b3 = b[boff + 3] & 0xff; + int i = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + f[off + j] = Float.intBitsToFloat(i); + boff += 4; + } + } else { + for (int j = 0; j < len; j++) { + int b0 = b[boff + 3]; + int b1 = b[boff + 2] & 0xff; + int b2 = b[boff + 1] & 0xff; + int b3 = b[boff + 0] & 0xff; + int i = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + f[off + j] = Float.intBitsToFloat(i); + boff += 4; + } + } + } + + private void toDoubles(byte[] b, double[] d, int off, int len) { + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < len; j++) { + int b0 = b[boff]; + int b1 = b[boff + 1] & 0xff; + int b2 = b[boff + 2] & 0xff; + int b3 = b[boff + 3] & 0xff; + int b4 = b[boff + 4]; + int b5 = b[boff + 5] & 0xff; + int b6 = b[boff + 6] & 0xff; + int b7 = b[boff + 7] & 0xff; + + int i0 = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + int i1 = (b4 << 24) | (b5 << 16) | (b6 << 8) | b7; + long l = ((long)i0 << 32) | (i1 & 0xffffffffL); + + d[off + j] = Double.longBitsToDouble(l); + boff += 8; + } + } else { + for (int j = 0; j < len; j++) { + int b0 = b[boff + 7]; + int b1 = b[boff + 6] & 0xff; + int b2 = b[boff + 5] & 0xff; + int b3 = b[boff + 4] & 0xff; + int b4 = b[boff + 3]; + int b5 = b[boff + 2] & 0xff; + int b6 = b[boff + 1] & 0xff; + int b7 = b[boff] & 0xff; + + int i0 = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + int i1 = (b4 << 24) | (b5 << 16) | (b6 << 8) | b7; + long l = ((long)i0 << 32) | (i1 & 0xffffffffL); + + d[off + j] = Double.longBitsToDouble(l); + boff += 8; + } + } + } + + public long getStreamPosition() throws IOException { + checkClosed(); + return streamPos; + } + + public int getBitOffset() throws IOException { + checkClosed(); + return bitOffset; + } + + public void setBitOffset(int bitOffset) throws IOException { + checkClosed(); + if (bitOffset < 0 || bitOffset > 7) { + throw new IllegalArgumentException("bitOffset must be betwwen 0 and 7!"); + } + this.bitOffset = bitOffset; + } + + public int readBit() throws IOException { + checkClosed(); + + // Compute final bit offset before we call read() and seek() + int newBitOffset = (this.bitOffset + 1) & 0x7; + + int val = read(); + if (val == -1) { + throw new EOFException(); + } + + if (newBitOffset != 0) { + // Move byte position back if in the middle of a byte + seek(getStreamPosition() - 1); + // Shift the bit to be read to the rightmost position + val >>= 8 - newBitOffset; + } + this.bitOffset = newBitOffset; + + return val & 0x1; + } + + public long readBits(int numBits) throws IOException { + checkClosed(); + + if (numBits < 0 || numBits > 64) { + throw new IllegalArgumentException(); + } + if (numBits == 0) { + return 0L; + } + + // Have to read additional bits on the left equal to the bit offset + int bitsToRead = numBits + bitOffset; + + // Compute final bit offset before we call read() and seek() + int newBitOffset = (this.bitOffset + numBits) & 0x7; + + // Read a byte at a time, accumulate + long accum = 0L; + while (bitsToRead > 0) { + int val = read(); + if (val == -1) { + throw new EOFException(); + } + + accum <<= 8; + accum |= val; + bitsToRead -= 8; + } + + // Move byte position back if in the middle of a byte + if (newBitOffset != 0) { + seek(getStreamPosition() - 1); + } + this.bitOffset = newBitOffset; + + // Shift away unwanted bits on the right. + accum >>>= (-bitsToRead); // Negative of bitsToRead == extra bits read + + // Mask out unwanted bits on the left + accum &= (-1L >>> (64 - numBits)); + + return accum; + } + + /** + * Returns -1L to indicate that the stream has unknown + * length. Subclasses must override this method to provide actual + * length information. + * + * @return -1L to indicate unknown length. + */ + public long length() { + return -1L; + } + + /** + * Advances the current stream position by calling + * seek(getStreamPosition() + n). + * + *

The bit offset is reset to zero. + * + * @param n the number of bytes to seek forward. + * + * @return an int representing the number of bytes + * skipped. + * + * @exception IOException if getStreamPosition + * throws an IOException when computing either + * the starting or ending position. + */ + public int skipBytes(int n) throws IOException { + long pos = getStreamPosition(); + seek(pos + n); + return (int)(getStreamPosition() - pos); + } + + /** + * Advances the current stream position by calling + * seek(getStreamPosition() + n). + * + *

The bit offset is reset to zero. + * + * @param n the number of bytes to seek forward. + * + * @return a long representing the number of bytes + * skipped. + * + * @exception IOException if getStreamPosition + * throws an IOException when computing either + * the starting or ending position. + */ + public long skipBytes(long n) throws IOException { + long pos = getStreamPosition(); + seek(pos + n); + return getStreamPosition() - pos; + } + + public void seek(long pos) throws IOException { + checkClosed(); + + // This test also covers pos < 0 + if (pos < flushedPos) { + throw new IndexOutOfBoundsException("pos < flushedPos!"); + } + + this.streamPos = pos; + this.bitOffset = 0; + } + + /** + * Pushes the current stream position onto a stack of marked + * positions. + */ + public void mark() { + try { + markByteStack.push(Long.valueOf(getStreamPosition())); + markBitStack.push(Integer.valueOf(getBitOffset())); + } catch (IOException e) { + } + } + + /** + * Resets the current stream byte and bit positions from the stack + * of marked positions. + * + *

An IOException will be thrown if the previous + * marked position lies in the discarded portion of the stream. + * + * @exception IOException if an I/O error occurs. + */ + public void reset() throws IOException { + if (markByteStack.empty()) { + return; + } + + long pos = ((Long)markByteStack.pop()).longValue(); + if (pos < flushedPos) { + throw new IIOException + ("Previous marked position has been discarded!"); + } + seek(pos); + + int offset = ((Integer)markBitStack.pop()).intValue(); + setBitOffset(offset); + } + + public void flushBefore(long pos) throws IOException { + checkClosed(); + if (pos < flushedPos) { + throw new IndexOutOfBoundsException("pos < flushedPos!"); + } + if (pos > getStreamPosition()) { + throw new IndexOutOfBoundsException("pos > getStreamPosition()!"); + } + // Invariant: flushedPos >= 0 + flushedPos = pos; + } + + public void flush() throws IOException { + flushBefore(getStreamPosition()); + } + + public long getFlushedPosition() { + return flushedPos; + } + + /** + * Default implementation returns false. Subclasses should + * override this if they cache data. + */ + public boolean isCached() { + return false; + } + + /** + * Default implementation returns false. Subclasses should + * override this if they cache data in main memory. + */ + public boolean isCachedMemory() { + return false; + } + + /** + * Default implementation returns false. Subclasses should + * override this if they cache data in a temporary file. + */ + public boolean isCachedFile() { + return false; + } + + public void close() throws IOException { + checkClosed(); + + isClosed = true; + } + + /** + * Finalizes this object prior to garbage collection. The + * close method is called to close any open input + * source. This method should not be called from application + * code. + * + * @exception Throwable if an error occurs during superclass + * finalization. + */ + protected void finalize() throws Throwable { + if (!isClosed) { + try { + close(); + } catch (IOException e) { + } + } + super.finalize(); + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageOutputStream.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageOutputStream.java index 4b9b778a9..7eb56f3a9 100644 --- a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageOutputStream.java +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageOutputStream.java @@ -1,11 +1,662 @@ +/* + * Copyright (c) 2000, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package javax.imageio.stream; import java.io.DataOutput; +import java.io.IOException; /** - *SwingJS placeholder only + * A seekable output stream interface for use by + * ImageWriters. Various output destinations, such as + * OutputStreams and Files, as well as + * future fast I/O destinations may be "wrapped" by a suitable + * implementation of this interface for use by the Image I/O API. + * + *

Unlike a standard OutputStream, ImageOutputStream + * extends its counterpart, ImageInputStream. Thus it is + * possible to read from the stream as it is being written. The same + * seek and flush positions apply to both reading and writing, although + * the semantics for dealing with a non-zero bit offset before a byte-aligned + * write are necessarily different from the semantics for dealing with + * a non-zero bit offset before a byte-aligned read. When reading bytes, + * any bit offset is set to 0 before the read; when writing bytes, a + * non-zero bit offset causes the remaining bits in the byte to be written + * as 0s. The byte-aligned write then starts at the next byte position. + * + * @see ImageInputStream * */ public interface ImageOutputStream extends ImageInputStream, DataOutput { - + + /** + * Writes a single byte to the stream at the current position. + * The 24 high-order bits of b are ignored. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. Implementers can use the + * {@link ImageOutputStreamImpl#flushBits flushBits} + * method of {@link ImageOutputStreamImpl ImageOutputStreamImpl} + * to guarantee this. + * + * @param b an int whose lower 8 bits are to be + * written. + * + * @exception IOException if an I/O error occurs. + */ + void write(int b) throws IOException; + + /** + * Writes a sequence of bytes to the stream at the current + * position. If b.length is 0, nothing is written. + * The byte b[0] is written first, then the byte + * b[1], and so on. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param b an array of bytes to be written. + * + * @exception NullPointerException if b is + * null. + * @exception IOException if an I/O error occurs. + */ + void write(byte b[]) throws IOException; + + /** + * Writes a sequence of bytes to the stream at the current + * position. If len is 0, nothing is written. + * The byte b[off] is written first, then the byte + * b[off + 1], and so on. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. Implementers can use the + * {@link ImageOutputStreamImpl#flushBits flushBits} + * method of {@link ImageOutputStreamImpl ImageOutputStreamImpl} + * to guarantee this. + * + * @param b an array of bytes to be written. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than b.length. + * @exception NullPointerException if b is + * null. + * @exception IOException if an I/O error occurs. + */ + void write(byte b[], int off, int len) throws IOException; + + /** + * Writes a boolean value to the stream. If + * v is true, the value (byte)1 is + * written; if v is false, the value + * (byte)0 is written. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param v the boolean to be written. + * + * @exception IOException if an I/O error occurs. + */ + void writeBoolean(boolean v) throws IOException; + + /** + * Writes the 8 low-order bits of v to the + * stream. The 24 high-order bits of v are ignored. + * (This means that writeByte does exactly the same + * thing as write for an integer argument.) + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param v an int containing the byte value to be + * written. + * + * @exception IOException if an I/O error occurs. + */ + void writeByte(int v) throws IOException; + + /** + * Writes the 16 low-order bits of v to the + * stream. The 16 high-order bits of v are ignored. + * If the stream uses network byte order, the bytes written, in + * order, will be: + * + *

+     * (byte)((v >> 8) & 0xff)
+     * (byte)(v & 0xff)
+     * 
+ * + * Otherwise, the bytes written will be: + * + *
+     * (byte)(v & 0xff)
+     * (byte)((v >> 8) & 0xff)
+     * 
+ * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param v an int containing the short value to be + * written. + * + * @exception IOException if an I/O error occurs. + */ + void writeShort(int v) throws IOException; + + /** + * This method is a synonym for {@link #writeShort writeShort}. + * + * @param v an int containing the char (unsigned + * short) value to be written. + * + * @exception IOException if an I/O error occurs. + * + * @see #writeShort(int) + */ + void writeChar(int v) throws IOException; + + /** + * Writes the 32 bits of v to the stream. If the + * stream uses network byte order, the bytes written, in order, + * will be: + * + *

+     * (byte)((v >> 24) & 0xff)
+     * (byte)((v >> 16) & 0xff)
+     * (byte)((v >> 8) & 0xff)
+     * (byte)(v & 0xff)
+     * 
+ * + * Otheriwse, the bytes written will be: + * + *
+     * (byte)(v & 0xff)
+     * (byte)((v >> 8) & 0xff)
+     * (byte)((v >> 16) & 0xff)
+     * (byte)((v >> 24) & 0xff)
+     * 
+ * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param v an int containing the value to be + * written. + * + * @exception IOException if an I/O error occurs. + */ + void writeInt(int v) throws IOException; + + /** + * Writes the 64 bits of v to the stream. If the + * stream uses network byte order, the bytes written, in order, + * will be: + * + *

+     * (byte)((v >> 56) & 0xff)
+     * (byte)((v >> 48) & 0xff)
+     * (byte)((v >> 40) & 0xff)
+     * (byte)((v >> 32) & 0xff)
+     * (byte)((v >> 24) & 0xff)
+     * (byte)((v >> 16) & 0xff)
+     * (byte)((v >> 8) & 0xff)
+     * (byte)(v & 0xff)
+     * 
+ * + * Otherwise, the bytes written will be: + * + *
+     * (byte)(v & 0xff)
+     * (byte)((v >> 8) & 0xff)
+     * (byte)((v >> 16) & 0xff)
+     * (byte)((v >> 24) & 0xff)
+     * (byte)((v >> 32) & 0xff)
+     * (byte)((v >> 40) & 0xff)
+     * (byte)((v >> 48) & 0xff)
+     * (byte)((v >> 56) & 0xff)
+     * 
+ * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param v a long containing the value to be + * written. + * + * @exception IOException if an I/O error occurs. + */ + void writeLong(long v) throws IOException; + + /** + * Writes a float value, which is comprised of four + * bytes, to the output stream. It does this as if it first + * converts this float value to an int + * in exactly the manner of the Float.floatToIntBits + * method and then writes the int value in exactly the manner of + * the writeInt method. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param v a float containing the value to be + * written. + * + * @exception IOException if an I/O error occurs. + */ + void writeFloat(float v) throws IOException; + + /** + * Writes a double value, which is comprised of four + * bytes, to the output stream. It does this as if it first + * converts this double value to an long + * in exactly the manner of the + * Double.doubleToLongBits method and then writes the + * long value in exactly the manner of the writeLong + * method. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param v a double containing the value to be + * written. + * + * @exception IOException if an I/O error occurs. + */ + void writeDouble(double v) throws IOException; + + /** + * Writes a string to the output stream. For every character in + * the string s, taken in order, one byte is written + * to the output stream. If s is null, a + * NullPointerException is thrown. + * + *

If s.length is zero, then no bytes are + * written. Otherwise, the character s[0] is written + * first, then s[1], and so on; the last character + * written is s[s.length-1]. For each character, one + * byte is written, the low-order byte, in exactly the manner of + * the writeByte method. The high-order eight bits of + * each character in the string are ignored. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param s a String containing the value to be + * written. + * + * @exception NullPointerException if s is + * null. + * @exception IOException if an I/O error occurs. + */ + void writeBytes(String s) throws IOException; + + /** + * Writes a string to the output stream. For every character in + * the string s, taken in order, two bytes are + * written to the output stream, ordered according to the current + * byte order setting. If network byte order is being used, the + * high-order byte is written first; the order is reversed + * otherwise. If s is null, a + * NullPointerException is thrown. + * + *

If s.length is zero, then no bytes are + * written. Otherwise, the character s[0] is written + * first, then s[1], and so on; the last character + * written is s[s.length-1]. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param s a String containing the value to be + * written. + * + * @exception NullPointerException if s is + * null. + * @exception IOException if an I/O error occurs. + */ + void writeChars(String s) throws IOException; + + /** + * Writes two bytes of length information to the output stream in + * network byte order, followed by the + * modified + * UTF-8 + * representation of every character in the string s. + * If s is null, a + * NullPointerException is thrown. Each character in + * the string s is converted to a group of one, two, + * or three bytes, depending on the value of the character. + * + *

If a character c is in the range + * \u0001 through \u007f, it is + * represented by one byte: + * + *

+     * (byte)c
+     * 
+ * + *

If a character c is \u0000 or + * is in the range \u0080 through + * \u07ff, then it is represented by two bytes, + * to be written in the order shown: + * + *


+     * (byte)(0xc0 | (0x1f & (c >> 6)))
+     * (byte)(0x80 | (0x3f & c))
+     * 
+ * + *

If a character c is in the range + * \u0800 through uffff, then it is + * represented by three bytes, to be written in the order shown: + * + *


+     * (byte)(0xe0 | (0x0f & (c >> 12)))
+     * (byte)(0x80 | (0x3f & (c >> 6)))
+     * (byte)(0x80 | (0x3f & c))
+     * 
+ * + *

First, the total number of bytes needed to represent all + * the characters of s is calculated. If this number + * is larger than 65535, then a + * UTFDataFormatException is thrown. Otherwise, this + * length is written to the output stream in exactly the manner of + * the writeShort method; after this, the one-, two-, + * or three-byte representation of each character in the string + * s is written. + * + *

The current byte order setting is ignored. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + *

Note: This method should not be used in + * the implementation of image formats that use standard UTF-8, + * because the modified UTF-8 used here is incompatible with + * standard UTF-8. + * + * @param s a String containing the value to be + * written. + * + * @exception NullPointerException if s is + * null. + * @exception java.io.UTFDataFormatException if the modified UTF-8 + * representation of s requires more than 65536 bytes. + * @exception IOException if an I/O error occurs. + */ + void writeUTF(String s) throws IOException; + + /** + * Writes a sequence of shorts to the stream at the current + * position. If len is 0, nothing is written. + * The short s[off] is written first, then the short + * s[off + 1], and so on. The byte order of the + * stream is used to determine the order in which the individual + * bytes are written. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param s an array of shorts to be written. + * @param off the start offset in the data. + * @param len the number of shorts to write. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than s.length. + * @exception NullPointerException if s is + * null. + * @exception IOException if an I/O error occurs. + */ + void writeShorts(short[] s, int off, int len) throws IOException; + + /** + * Writes a sequence of chars to the stream at the current + * position. If len is 0, nothing is written. + * The char c[off] is written first, then the char + * c[off + 1], and so on. The byte order of the + * stream is used to determine the order in which the individual + * bytes are written. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param c an array of chars to be written. + * @param off the start offset in the data. + * @param len the number of chars to write. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than c.length. + * @exception NullPointerException if c is + * null. + * @exception IOException if an I/O error occurs. + */ + void writeChars(char[] c, int off, int len) throws IOException; + + /** + * Writes a sequence of ints to the stream at the current + * position. If len is 0, nothing is written. + * The int i[off] is written first, then the int + * i[off + 1], and so on. The byte order of the + * stream is used to determine the order in which the individual + * bytes are written. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param i an array of ints to be written. + * @param off the start offset in the data. + * @param len the number of ints to write. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than i.length. + * @exception NullPointerException if i is + * null. + * @exception IOException if an I/O error occurs. + */ + void writeInts(int[] i, int off, int len) throws IOException; + + /** + * Writes a sequence of longs to the stream at the current + * position. If len is 0, nothing is written. + * The long l[off] is written first, then the long + * l[off + 1], and so on. The byte order of the + * stream is used to determine the order in which the individual + * bytes are written. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param l an array of longs to be written. + * @param off the start offset in the data. + * @param len the number of longs to write. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than l.length. + * @exception NullPointerException if l is + * null. + * @exception IOException if an I/O error occurs. + */ + void writeLongs(long[] l, int off, int len) throws IOException; + + /** + * Writes a sequence of floats to the stream at the current + * position. If len is 0, nothing is written. + * The float f[off] is written first, then the float + * f[off + 1], and so on. The byte order of the + * stream is used to determine the order in which the individual + * bytes are written. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param f an array of floats to be written. + * @param off the start offset in the data. + * @param len the number of floats to write. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than f.length. + * @exception NullPointerException if f is + * null. + * @exception IOException if an I/O error occurs. + */ + void writeFloats(float[] f, int off, int len) throws IOException; + + /** + * Writes a sequence of doubles to the stream at the current + * position. If len is 0, nothing is written. + * The double d[off] is written first, then the double + * d[off + 1], and so on. The byte order of the + * stream is used to determine the order in which the individual + * bytes are written. + * + *

If the bit offset within the stream is non-zero, the + * remainder of the current byte is padded with 0s + * and written out first. The bit offset will be 0 after the + * write. + * + * @param d an array of doubless to be written. + * @param off the start offset in the data. + * @param len the number of doubles to write. + * + * @exception IndexOutOfBoundsException if off is + * negative, len is negative, or off + + * len is greater than d.length. + * @exception NullPointerException if d is + * null. + * @exception IOException if an I/O error occurs. + */ + void writeDoubles(double[] d, int off, int len) throws IOException; + + /** + * Writes a single bit, given by the least significant bit of the + * argument, to the stream at the current bit offset within the + * current byte position. The upper 31 bits of the argument are + * ignored. The given bit replaces the previous bit at that + * position. The bit offset is advanced by one and reduced modulo + * 8. + * + *

If any bits of a particular byte have never been set + * at the time the byte is flushed to the destination, those + * bits will be set to 0 automatically. + * + * @param bit an int whose least significant bit + * is to be written to the stream. + * + * @exception IOException if an I/O error occurs. + */ + void writeBit(int bit) throws IOException; + + /** + * Writes a sequence of bits, given by the numBits + * least significant bits of the bits argument in + * left-to-right order, to the stream at the current bit offset + * within the current byte position. The upper 64 - + * numBits bits of the argument are ignored. The bit + * offset is advanced by numBits and reduced modulo + * 8. Note that a bit offset of 0 always indicates the + * most-significant bit of the byte, and bytes of bits are written + * out in sequence as they are encountered. Thus bit writes are + * always effectively in network byte order. The actual stream + * byte order setting is ignored. + * + *

Bit data may be accumulated in memory indefinitely, until + * flushBefore is called. At that time, all bit data + * prior to the flushed position will be written. + * + *

If any bits of a particular byte have never been set + * at the time the byte is flushed to the destination, those + * bits will be set to 0 automatically. + * + * @param bits a long containing the bits to be + * written, starting with the bit in position numBits - + * 1 down to the least significant bit. + * + * @param numBits an int between 0 and 64, inclusive. + * + * @exception IllegalArgumentException if numBits is + * not between 0 and 64, inclusive. + * @exception IOException if an I/O error occurs. + */ + void writeBits(long bits, int numBits) throws IOException; + + /** + * Flushes all data prior to the given position to the underlying + * destination, such as an OutputStream or + * File. Attempting to seek to the flushed portion + * of the stream will result in an + * IndexOutOfBoundsException. + * + * @param pos a long containing the length of the + * stream prefix that may be flushed to the destination. + * + * @exception IndexOutOfBoundsException if pos lies + * in the flushed portion of the stream or past the current stream + * position. + * @exception IOException if an I/O error occurs. + */ + void flushBefore(long pos) throws IOException; } diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageOutputStreamImpl.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageOutputStreamImpl.java new file mode 100644 index 000000000..f53925ab0 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/ImageOutputStreamImpl.java @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2000, 2007, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.stream; + +import java.io.IOException; +import java.io.UTFDataFormatException; +import java.nio.ByteOrder; + +/** + * An abstract class implementing the ImageOutputStream interface. + * This class is designed to reduce the number of methods that must + * be implemented by subclasses. + * + */ +public abstract class ImageOutputStreamImpl + extends ImageInputStreamImpl + implements ImageOutputStream { + + /** + * Constructs an ImageOutputStreamImpl. + */ + public ImageOutputStreamImpl() { + } + + public abstract void write(int b) throws IOException; + + public void write(byte b[]) throws IOException { + write(b, 0, b.length); + } + + public abstract void write(byte b[], int off, int len) throws IOException; + + public void writeBoolean(boolean v) throws IOException { + write(v ? 1 : 0); + } + + public void writeByte(int v) throws IOException { + write(v); + } + + public void writeShort(int v) throws IOException { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + byteBuf[0] = (byte)(v >>> 8); + byteBuf[1] = (byte)(v >>> 0); + } else { + byteBuf[0] = (byte)(v >>> 0); + byteBuf[1] = (byte)(v >>> 8); + } + write(byteBuf, 0, 2); + } + + public void writeChar(int v) throws IOException { + writeShort(v); + } + + public void writeInt(int v) throws IOException { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + byteBuf[0] = (byte)(v >>> 24); + byteBuf[1] = (byte)(v >>> 16); + byteBuf[2] = (byte)(v >>> 8); + byteBuf[3] = (byte)(v >>> 0); + } else { + byteBuf[0] = (byte)(v >>> 0); + byteBuf[1] = (byte)(v >>> 8); + byteBuf[2] = (byte)(v >>> 16); + byteBuf[3] = (byte)(v >>> 24); + } + write(byteBuf, 0, 4); + } + + public void writeLong(long v) throws IOException { + if (byteOrder == ByteOrder.BIG_ENDIAN) { + byteBuf[0] = (byte)(v >>> 56); + byteBuf[1] = (byte)(v >>> 48); + byteBuf[2] = (byte)(v >>> 40); + byteBuf[3] = (byte)(v >>> 32); + byteBuf[4] = (byte)(v >>> 24); + byteBuf[5] = (byte)(v >>> 16); + byteBuf[6] = (byte)(v >>> 8); + byteBuf[7] = (byte)(v >>> 0); + } else { + byteBuf[0] = (byte)(v >>> 0); + byteBuf[1] = (byte)(v >>> 8); + byteBuf[2] = (byte)(v >>> 16); + byteBuf[3] = (byte)(v >>> 24); + byteBuf[4] = (byte)(v >>> 32); + byteBuf[5] = (byte)(v >>> 40); + byteBuf[6] = (byte)(v >>> 48); + byteBuf[7] = (byte)(v >>> 56); + } + // REMIND: Once 6277756 is fixed, we should do a bulk write of all 8 + // bytes here as we do in writeShort() and writeInt() for even better + // performance. For now, two bulk writes of 4 bytes each is still + // faster than 8 individual write() calls (see 6347575 for details). + write(byteBuf, 0, 4); + write(byteBuf, 4, 4); + } + + public void writeFloat(float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + public void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + public void writeBytes(String s) throws IOException { + int len = s.length(); + for (int i = 0 ; i < len ; i++) { + write((byte)s.charAt(i)); + } + } + + public void writeChars(String s) throws IOException { + int len = s.length(); + + byte[] b = new byte[len*2]; + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < len ; i++) { + int v = s.charAt(i); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 0); + } + } else { + for (int i = 0; i < len ; i++) { + int v = s.charAt(i); + b[boff++] = (byte)(v >>> 0); + b[boff++] = (byte)(v >>> 8); + } + } + + write(b, 0, len*2); + } + + public void writeUTF(String s) throws IOException { + int strlen = s.length(); + int utflen = 0; + char[] charr = new char[strlen]; + int c, boff = 0; + + s.getChars(0, strlen, charr, 0); + + for (int i = 0; i < strlen; i++) { + c = charr[i]; + if ((c >= 0x0001) && (c <= 0x007F)) { + utflen++; + } else if (c > 0x07FF) { + utflen += 3; + } else { + utflen += 2; + } + } + + if (utflen > 65535) { + throw new UTFDataFormatException("utflen > 65536!"); + } + + byte[] b = new byte[utflen+2]; + b[boff++] = (byte) ((utflen >>> 8) & 0xFF); + b[boff++] = (byte) ((utflen >>> 0) & 0xFF); + for (int i = 0; i < strlen; i++) { + c = charr[i]; + if ((c >= 0x0001) && (c <= 0x007F)) { + b[boff++] = (byte) c; + } else if (c > 0x07FF) { + b[boff++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); + b[boff++] = (byte) (0x80 | ((c >> 6) & 0x3F)); + b[boff++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } else { + b[boff++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); + b[boff++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } + } + write(b, 0, utflen + 2); + } + + public void writeShorts(short[] s, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > s.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > s.length!"); + } + + byte[] b = new byte[len*2]; + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < len; i++) { + short v = s[off + i]; + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 0); + } + } else { + for (int i = 0; i < len; i++) { + short v = s[off + i]; + b[boff++] = (byte)(v >>> 0); + b[boff++] = (byte)(v >>> 8); + } + } + + write(b, 0, len*2); + } + + public void writeChars(char[] c, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > c.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > c.length!"); + } + + byte[] b = new byte[len*2]; + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < len; i++) { + char v = c[off + i]; + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 0); + } + } else { + for (int i = 0; i < len; i++) { + char v = c[off + i]; + b[boff++] = (byte)(v >>> 0); + b[boff++] = (byte)(v >>> 8); + } + } + + write(b, 0, len*2); + } + + public void writeInts(int[] i, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > i.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > i.length!"); + } + + byte[] b = new byte[len*4]; + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < len; j++) { + int v = i[off + j]; + b[boff++] = (byte)(v >>> 24); + b[boff++] = (byte)(v >>> 16); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 0); + } + } else { + for (int j = 0; j < len; j++) { + int v = i[off + j]; + b[boff++] = (byte)(v >>> 0); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 16); + b[boff++] = (byte)(v >>> 24); + } + } + + write(b, 0, len*4); + } + + public void writeLongs(long[] l, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > l.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > l.length!"); + } + + byte[] b = new byte[len*8]; + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < len; i++) { + long v = l[off + i]; + b[boff++] = (byte)(v >>> 56); + b[boff++] = (byte)(v >>> 48); + b[boff++] = (byte)(v >>> 40); + b[boff++] = (byte)(v >>> 32); + b[boff++] = (byte)(v >>> 24); + b[boff++] = (byte)(v >>> 16); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 0); + } + } else { + for (int i = 0; i < len; i++) { + long v = l[off + i]; + b[boff++] = (byte)(v >>> 0); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 16); + b[boff++] = (byte)(v >>> 24); + b[boff++] = (byte)(v >>> 32); + b[boff++] = (byte)(v >>> 40); + b[boff++] = (byte)(v >>> 48); + b[boff++] = (byte)(v >>> 56); + } + } + + write(b, 0, len*8); + } + + public void writeFloats(float[] f, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > f.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > f.length!"); + } + + byte[] b = new byte[len*4]; + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < len; i++) { + int v = Float.floatToIntBits(f[off + i]); + b[boff++] = (byte)(v >>> 24); + b[boff++] = (byte)(v >>> 16); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 0); + } + } else { + for (int i = 0; i < len; i++) { + int v = Float.floatToIntBits(f[off + i]); + b[boff++] = (byte)(v >>> 0); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 16); + b[boff++] = (byte)(v >>> 24); + } + } + + write(b, 0, len*4); + } + + public void writeDoubles(double[] d, int off, int len) throws IOException { + // Fix 4430357 - if off + len < 0, overflow occurred + if (off < 0 || len < 0 || off + len > d.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off + len > d.length!"); + } + + byte[] b = new byte[len*8]; + int boff = 0; + if (byteOrder == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < len; i++) { + long v = Double.doubleToLongBits(d[off + i]); + b[boff++] = (byte)(v >>> 56); + b[boff++] = (byte)(v >>> 48); + b[boff++] = (byte)(v >>> 40); + b[boff++] = (byte)(v >>> 32); + b[boff++] = (byte)(v >>> 24); + b[boff++] = (byte)(v >>> 16); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 0); + } + } else { + for (int i = 0; i < len; i++) { + long v = Double.doubleToLongBits(d[off + i]); + b[boff++] = (byte)(v >>> 0); + b[boff++] = (byte)(v >>> 8); + b[boff++] = (byte)(v >>> 16); + b[boff++] = (byte)(v >>> 24); + b[boff++] = (byte)(v >>> 32); + b[boff++] = (byte)(v >>> 40); + b[boff++] = (byte)(v >>> 48); + b[boff++] = (byte)(v >>> 56); + } + } + + write(b, 0, len*8); + } + + public void writeBit(int bit) throws IOException { + writeBits((1L & bit), 1); + } + + public void writeBits(long bits, int numBits) throws IOException { + checkClosed(); + + if (numBits < 0 || numBits > 64) { + throw new IllegalArgumentException("Bad value for numBits!"); + } + if (numBits == 0) { + return; + } + + // Prologue: deal with pre-existing bits + + // Bug 4499158, 4507868 - if we're at the beginning of the stream + // and the bit offset is 0, there can't be any pre-existing bits + if ((getStreamPosition() > 0) || (bitOffset > 0)) { + int offset = bitOffset; // read() will reset bitOffset + int partialByte = read(); + if (partialByte != -1) { + seek(getStreamPosition() - 1); + } else { + partialByte = 0; + } + + if (numBits + offset < 8) { + // Notch out the partial byte and drop in the new bits + int shift = 8 - (offset+numBits); + int mask = -1 >>> (32 - numBits); + partialByte &= ~(mask << shift); // Clear out old bits + partialByte |= ((bits & mask) << shift); // Or in new ones + write(partialByte); + seek(getStreamPosition() - 1); + bitOffset = offset + numBits; + numBits = 0; // Signal that we are done + } else { + // Fill out the partial byte and reduce numBits + int num = 8 - offset; + int mask = -1 >>> (32 - num); + partialByte &= ~mask; // Clear out bits + partialByte |= ((bits >> (numBits - num)) & mask); + // Note that bitOffset is already 0, so there is no risk + // of this advancing to the next byte + write(partialByte); + numBits -= num; + } + } + + // Now write any whole bytes + if (numBits > 7) { + int extra = numBits % 8; + for (int numBytes = numBits / 8; numBytes > 0; numBytes--) { + int shift = (numBytes-1)*8+extra; + int value = (int) ((shift == 0) + ? bits & 0xFF + : (bits>>shift) & 0xFF); + write(value); + } + numBits = extra; + } + + // Epilogue: write out remaining partial byte, if any + // Note that we may be at EOF, in which case we pad with 0, + // or not, in which case we must preserve the existing bits + if (numBits != 0) { + // If we are not at the end of the file, read the current byte + // If we are at the end of the file, initialize our byte to 0. + int partialByte = 0; + partialByte = read(); + if (partialByte != -1) { + seek(getStreamPosition() - 1); + } + // Fix 4494976: writeBit(int) does not pad the remainder + // of the current byte with 0s + else { // EOF + partialByte = 0; + } + + int shift = 8 - numBits; + int mask = -1 >>> (32 - numBits); + partialByte &= ~(mask << shift); + partialByte |= (bits & mask) << shift; + // bitOffset is always already 0 when we get here. + write(partialByte); + seek(getStreamPosition() - 1); + bitOffset = numBits; + } + } + + /** + * If the bit offset is non-zero, forces the remaining bits + * in the current byte to 0 and advances the stream position + * by one. This method should be called by subclasses at the + * beginning of the write(int) and + * write(byte[], int, int) methods. + * + * @exception IOException if an I/O error occurs. + */ + protected final void flushBits() throws IOException { + checkClosed(); + if (bitOffset != 0) { + int offset = bitOffset; + int partialByte = read(); // Sets bitOffset to 0 + if (partialByte < 0) { + // Fix 4465683: When bitOffset is set + // to something non-zero beyond EOF, + // we should set that whole byte to + // zero and write it to stream. + partialByte = 0; + bitOffset = 0; + } + else { + seek(getStreamPosition() - 1); + partialByte &= -1 << (8 - offset); + } + write(partialByte); + } + } + +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCache.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCache.java new file mode 100644 index 000000000..b7102d7f0 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCache.java @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2000, 2003, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.stream; + +import java.util.ArrayList; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +/** + * Package-visible class consolidating common code for + * MemoryCacheImageInputStream and + * MemoryCacheImageOutputStream. + * This class keeps an ArrayList of 8K blocks, + * loaded sequentially. Blocks may only be disposed of + * from the index 0 forward. As blocks are freed, the + * corresponding entries in the array list are set to + * null, but no compacting is performed. + * This allows the index for each block to never change, + * and the length of the cache is always the same as the + * total amount of data ever cached. Cached data is + * therefore always contiguous from the point of last + * disposal to the current length. + * + *

The total number of blocks resident in the cache must not + * exceed Integer.MAX_VALUE. In practice, the limit of + * available memory will be exceeded long before this becomes an + * issue, since a full cache would contain 8192*2^31 = 16 terabytes of + * data. + * + * A MemoryCache may be reused after a call + * to reset(). + */ +class MemoryCache { + + private static final int BUFFER_LENGTH = 8192; + + private ArrayList cache = new ArrayList(); + + private long cacheStart = 0L; + + /** + * The largest position ever written to the cache. + */ + private long length = 0L; + + private byte[] getCacheBlock(long blockNum) throws IOException { + long blockOffset = blockNum - cacheStart; + if (blockOffset > Integer.MAX_VALUE) { + // This can only happen when the cache hits 16 terabytes of + // contiguous data... + throw new IOException("Cache addressing limit exceeded!"); + } + return (byte[])cache.get((int)blockOffset); + } + + /** + * Ensures that at least pos bytes are cached, + * or the end of the source is reached. The return value + * is equal to the smaller of pos and the + * length of the source. + */ + public long loadFromStream(InputStream stream, long pos) + throws IOException { + // We've already got enough data cached + if (pos < length) { + return pos; + } + + int offset = (int)(length % BUFFER_LENGTH); + byte [] buf = null; + + long len = pos - length; + if (offset != 0) { + buf = getCacheBlock(length/BUFFER_LENGTH); + } + + while (len > 0) { + if (buf == null) { + try { + buf = new byte[BUFFER_LENGTH]; + } catch (OutOfMemoryError e) { + throw new IOException("No memory left for cache!"); + } + offset = 0; + } + + int left = BUFFER_LENGTH - offset; + int nbytes = (int)Math.min(len, (long)left); + nbytes = stream.read(buf, offset, nbytes); + if (nbytes == -1) { + return length; // EOF + } + + if (offset == 0) { + cache.add(buf); + } + + len -= nbytes; + length += nbytes; + offset += nbytes; + + if (offset >= BUFFER_LENGTH) { + // we've filled the current buffer, so a new one will be + // allocated next time around (and offset will be reset to 0) + buf = null; + } + } + + return pos; + } + + /** + * Writes out a portion of the cache to an OutputStream. + * This method preserves no state about the output stream, and does + * not dispose of any blocks containing bytes written. To dispose + * blocks, use {@link #disposeBefore disposeBefore()}. + * + * @exception IndexOutOfBoundsException if any portion of + * the requested data is not in the cache (including if pos + * is in a block already disposed), or if either pos or + * len is < 0. + */ + public void writeToStream(OutputStream stream, long pos, long len) + throws IOException { + if (pos + len > length) { + throw new IndexOutOfBoundsException("Argument out of cache"); + } + if ((pos < 0) || (len < 0)) { + throw new IndexOutOfBoundsException("Negative pos or len"); + } + if (len == 0) { + return; + } + + long bufIndex = pos/BUFFER_LENGTH; + if (bufIndex < cacheStart) { + throw new IndexOutOfBoundsException("pos already disposed"); + } + int offset = (int)(pos % BUFFER_LENGTH); + + byte[] buf = getCacheBlock(bufIndex++); + while (len > 0) { + if (buf == null) { + buf = getCacheBlock(bufIndex++); + offset = 0; + } + int nbytes = (int)Math.min(len, (long)(BUFFER_LENGTH - offset)); + stream.write(buf, offset, nbytes); + buf = null; + len -= nbytes; + } + } + + /** + * Ensure that there is space to write a byte at the given position. + */ + private void pad(long pos) throws IOException { + long currIndex = cacheStart + cache.size() - 1; + long lastIndex = pos/BUFFER_LENGTH; + long numNewBuffers = lastIndex - currIndex; + for (long i = 0; i < numNewBuffers; i++) { + try { + cache.add(new byte[BUFFER_LENGTH]); + } catch (OutOfMemoryError e) { + throw new IOException("No memory left for cache!"); + } + } + } + + /** + * Overwrites and/or appends the cache from a byte array. + * The length of the cache will be extended as needed to hold + * the incoming data. + * + * @param b an array of bytes containing data to be written. + * @param off the starting offset withing the data array. + * @param len the number of bytes to be written. + * @param pos the cache position at which to begin writing. + * + * @exception NullPointerException if b is null. + * @exception IndexOutOfBoundsException if off, + * len, or pos are negative, + * or if off+len > b.length. + */ + public void write(byte[] b, int off, int len, long pos) + throws IOException { + if (b == null) { + throw new NullPointerException("b == null!"); + } + // Fix 4430357 - if off + len < 0, overflow occurred + if ((off < 0) || (len < 0) || (pos < 0) || + (off + len > b.length) || (off + len < 0)) { + throw new IndexOutOfBoundsException(); + } + + // Ensure there is space for the incoming data + long lastPos = pos + len - 1; + if (lastPos >= length) { + pad(lastPos); + length = lastPos + 1; + } + + // Copy the data into the cache, block by block + int offset = (int)(pos % BUFFER_LENGTH); + while (len > 0) { + byte[] buf = getCacheBlock(pos/BUFFER_LENGTH); + int nbytes = Math.min(len, BUFFER_LENGTH - offset); + System.arraycopy(b, off, buf, offset, nbytes); + + pos += nbytes; + off += nbytes; + len -= nbytes; + offset = 0; // Always after the first time + } + } + + /** + * Overwrites or appends a single byte to the cache. + * The length of the cache will be extended as needed to hold + * the incoming data. + * + * @param b an int whose 8 least significant bits + * will be written. + * @param pos the cache position at which to begin writing. + * + * @exception IndexOutOfBoundsException if pos is negative. + */ + public void write(int b, long pos) throws IOException { + if (pos < 0) { + throw new ArrayIndexOutOfBoundsException("pos < 0"); + } + + // Ensure there is space for the incoming data + if (pos >= length) { + pad(pos); + length = pos + 1; + } + + // Insert the data. + byte[] buf = getCacheBlock(pos/BUFFER_LENGTH); + int offset = (int)(pos % BUFFER_LENGTH); + buf[offset] = (byte)b; + } + + /** + * Returns the total length of data that has been cached, + * regardless of whether any early blocks have been disposed. + * This value will only ever increase. + */ + public long getLength() { + return length; + } + + /** + * Returns the single byte at the given position, as an + * int. Returns -1 if this position has + * not been cached or has been disposed. + */ + public int read(long pos) throws IOException { + if (pos >= length) { + return -1; + } + + byte[] buf = getCacheBlock(pos/BUFFER_LENGTH); + if (buf == null) { + return -1; + } + + return buf[(int)(pos % BUFFER_LENGTH)] & 0xff; + } + + /** + * Copy len bytes from the cache, starting + * at cache position pos, into the array + * b at offset off. + * + * @exception NullPointerException if b is null + * @exception IndexOutOfBoundsException if off, + * len or pos are negative or if + * off + len > b.length or if any portion of the + * requested data is not in the cache (including if + * pos is in a block that has already been disposed). + */ + public void read(byte[] b, int off, int len, long pos) + throws IOException { + if (b == null) { + throw new NullPointerException("b == null!"); + } + // Fix 4430357 - if off + len < 0, overflow occurred + if ((off < 0) || (len < 0) || (pos < 0) || + (off + len > b.length) || (off + len < 0)) { + throw new IndexOutOfBoundsException(); + } + if (pos + len > length) { + throw new IndexOutOfBoundsException(); + } + + long index = pos/BUFFER_LENGTH; + int offset = (int)pos % BUFFER_LENGTH; + while (len > 0) { + int nbytes = Math.min(len, BUFFER_LENGTH - offset); + byte[] buf = getCacheBlock(index++); + System.arraycopy(buf, offset, b, off, nbytes); + + len -= nbytes; + off += nbytes; + offset = 0; // Always after the first time + } + } + + /** + * Free the blocks up to the position pos. + * The byte at pos remains available. + * + * @exception IndexOutOfBoundsException if pos + * is in a block that has already been disposed. + */ + public void disposeBefore(long pos) { + long index = pos/BUFFER_LENGTH; + if (index < cacheStart) { + throw new IndexOutOfBoundsException("pos already disposed"); + } + long numBlocks = Math.min(index - cacheStart, cache.size()); + for (long i = 0; i < numBlocks; i++) { + cache.remove(0); + } + this.cacheStart = index; + } + + /** + * Erase the entire cache contents and reset the length to 0. + * The cache object may subsequently be reused as though it had just + * been allocated. + */ + public void reset() { + cache.clear(); + cacheStart = 0; + length = 0L; + } + } diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCacheImageInputStream.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCacheImageInputStream.java new file mode 100644 index 000000000..f7a5375b7 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCacheImageInputStream.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2000, 2007, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.stream; + +import java.io.InputStream; +import java.io.IOException; +import com.sun.imageio.stream.StreamFinalizer; +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; + +/** + * An implementation of ImageInputStream that gets its + * input from a regular InputStream. A memory buffer is + * used to cache at least the data between the discard position and + * the current read position. + * + *

In general, it is preferable to use a + * FileCacheImageInputStream when reading from a regular + * InputStream. This class is provided for cases where + * it is not possible to create a writable temporary file. + * + */ +public class MemoryCacheImageInputStream extends ImageInputStreamImpl { + + private InputStream stream; + + private MemoryCache cache = new MemoryCache(); + + /** The referent to be registered with the Disposer. */ + private final Object disposerReferent; + + /** The DisposerRecord that resets the underlying MemoryCache. */ + private final DisposerRecord disposerRecord; + + /** + * Constructs a MemoryCacheImageInputStream that will read + * from a given InputStream. + * + * @param stream an InputStream to read from. + * + * @exception IllegalArgumentException if stream is + * null. + */ + public MemoryCacheImageInputStream(InputStream stream) { + if (stream == null) { + throw new IllegalArgumentException("stream == null!"); + } + this.stream = stream; + + disposerRecord = new StreamDisposerRecord(cache); + if (getClass() == MemoryCacheImageInputStream.class) { + disposerReferent = new Object(); + Disposer.addRecord(disposerReferent, disposerRecord); + } else { + disposerReferent = new StreamFinalizer(this); + } + } + + public int read() throws IOException { + checkClosed(); + bitOffset = 0; + long pos = cache.loadFromStream(stream, streamPos+1); + if (pos >= streamPos+1) { + return cache.read(streamPos++); + } else { + return -1; + } + } + + public int read(byte[] b, int off, int len) throws IOException { + checkClosed(); + + if (b == null) { + throw new NullPointerException("b == null!"); + } + if (off < 0 || len < 0 || off + len > b.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off+len > b.length || off+len < 0!"); + } + + bitOffset = 0; + + if (len == 0) { + return 0; + } + + long pos = cache.loadFromStream(stream, streamPos+len); + + len = (int)(pos - streamPos); // In case stream ended early + + if (len > 0) { + cache.read(b, off, len, streamPos); + streamPos += len; + return len; + } else { + return -1; + } + } + + public void flushBefore(long pos) throws IOException { + super.flushBefore(pos); // this will call checkClosed() for us + cache.disposeBefore(pos); + } + + /** + * Returns true since this + * ImageInputStream caches data in order to allow + * seeking backwards. + * + * @return true. + * + * @see #isCachedMemory + * @see #isCachedFile + */ + public boolean isCached() { + return true; + } + + /** + * Returns false since this + * ImageInputStream does not maintain a file cache. + * + * @return false. + * + * @see #isCached + * @see #isCachedMemory + */ + public boolean isCachedFile() { + return false; + } + + /** + * Returns true since this + * ImageInputStream maintains a main memory cache. + * + * @return true. + * + * @see #isCached + * @see #isCachedFile + */ + public boolean isCachedMemory() { + return true; + } + + /** + * Closes this MemoryCacheImageInputStream, freeing + * the cache. The source InputStream is not closed. + */ + public void close() throws IOException { + super.close(); + disposerRecord.dispose(); // this resets the MemoryCache + stream = null; + cache = null; + } + + /** + * {@inheritDoc} + */ + protected void finalize() throws Throwable { + // Empty finalizer: for performance reasons we instead use the + // Disposer mechanism for ensuring that the underlying + // MemoryCache is reset prior to garbage collection + } + + private static class StreamDisposerRecord implements DisposerRecord { + private MemoryCache cache; + + public StreamDisposerRecord(MemoryCache cache) { + this.cache = cache; + } + + public synchronized void dispose() { + if (cache != null) { + cache.reset(); + cache = null; + } + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCacheImageOutputStream.java b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCacheImageOutputStream.java new file mode 100644 index 000000000..9cdf701a9 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/javax/imageio/stream/MemoryCacheImageOutputStream.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2000, 2006, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.imageio.stream; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An implementation of ImageOutputStream that writes its + * output to a regular OutputStream. A memory buffer is + * used to cache at least the data between the discard position and + * the current write position. The only constructor takes an + * OutputStream, so this class may not be used for + * read/modify/write operations. Reading can occur only on parts of + * the stream that have already been written to the cache and not + * yet flushed. + * + */ +public class MemoryCacheImageOutputStream extends ImageOutputStreamImpl { + + private OutputStream stream; + + private MemoryCache cache = new MemoryCache(); + + /** + * Constructs a MemoryCacheImageOutputStream that will write + * to a given OutputStream. + * + * @param stream an OutputStream to write to. + * + * @exception IllegalArgumentException if stream is + * null. + */ + public MemoryCacheImageOutputStream(OutputStream stream) { + if (stream == null) { + throw new IllegalArgumentException("stream == null!"); + } + this.stream = stream; + } + + public int read() throws IOException { + checkClosed(); + + bitOffset = 0; + + int val = cache.read(streamPos); + if (val != -1) { + ++streamPos; + } + return val; + } + + public int read(byte[] b, int off, int len) throws IOException { + checkClosed(); + + if (b == null) { + throw new NullPointerException("b == null!"); + } + // Fix 4467608: read([B,I,I) works incorrectly if len<=0 + if (off < 0 || len < 0 || off + len > b.length || off + len < 0) { + throw new IndexOutOfBoundsException + ("off < 0 || len < 0 || off+len > b.length || off+len < 0!"); + } + + bitOffset = 0; + + if (len == 0) { + return 0; + } + + // check if we're already at/past EOF i.e. + // no more bytes left to read from cache + long bytesLeftInCache = cache.getLength() - streamPos; + if (bytesLeftInCache <= 0) { + return -1; // EOF + } + + // guaranteed by now that bytesLeftInCache > 0 && len > 0 + // and so the rest of the error checking is done by cache.read() + // NOTE that alot of error checking is duplicated + len = (int)Math.min(bytesLeftInCache, (long)len); + cache.read(b, off, len, streamPos); + streamPos += len; + return len; + } + + public void write(int b) throws IOException { + flushBits(); // this will call checkClosed() for us + cache.write(b, streamPos); + ++streamPos; + } + + public void write(byte[] b, int off, int len) throws IOException { + flushBits(); // this will call checkClosed() for us + cache.write(b, off, len, streamPos); + streamPos += len; + } + + public long length() { + try { + checkClosed(); + return cache.getLength(); + } catch (IOException e) { + return -1L; + } + } + + /** + * Returns true since this + * ImageOutputStream caches data in order to allow + * seeking backwards. + * + * @return true. + * + * @see #isCachedMemory + * @see #isCachedFile + */ + public boolean isCached() { + return true; + } + + /** + * Returns false since this + * ImageOutputStream does not maintain a file cache. + * + * @return false. + * + * @see #isCached + * @see #isCachedMemory + */ + public boolean isCachedFile() { + return false; + } + + /** + * Returns true since this + * ImageOutputStream maintains a main memory cache. + * + * @return true. + * + * @see #isCached + * @see #isCachedFile + */ + public boolean isCachedMemory() { + return true; + } + + /** + * Closes this MemoryCacheImageOutputStream. All + * pending data is flushed to the output, and the cache + * is released. The destination OutputStream + * is not closed. + */ + public void close() throws IOException { + long length = cache.getLength(); + seek(length); + flushBefore(length); + super.close(); + cache.reset(); + cache = null; + stream = null; + } + + public void flushBefore(long pos) throws IOException { + long oFlushedPos = flushedPos; + super.flushBefore(pos); // this will call checkClosed() for us + + long flushBytes = flushedPos - oFlushedPos; + cache.writeToStream(stream, oFlushedPos, flushBytes); + cache.disposeBefore(flushedPos); + stream.flush(); + } +} diff --git a/sources/net.sf.j2s.java.core/src/sun/awt/image/ByteBandedRaster.java b/sources/net.sf.j2s.java.core/src/sun/awt/image/ByteBandedRaster.java new file mode 100644 index 000000000..62dbcc69a --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/awt/image/ByteBandedRaster.java @@ -0,0 +1,834 @@ +/* + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.awt.image; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.RasterFormatException; +import java.awt.image.SampleModel; +import java.awt.image.BandedSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.Rectangle; +import java.awt.Point; + +/** + * This class defines a Raster with pixels consisting of multiple + * 8-bit samples stored in possibly separate arrays for each band. + * Operations on sets of pixels are performed on a given band in the + * Raster before moving on to the next band. The arrays used for + * storage may be distinct or shared between some or all of the bands. + * Each band additionally has an offset that is added to determine the + * DataBuffer location of each pixel. + * + * There is only one scanline stride for all bands. The pixel stride + * is always equal to one. This type of raster can be used with a + * ComponentColorModel. This class requires a BandedSampleModel. + * + */ +public class ByteBandedRaster extends SunWritableRaster { + + /** Data offsets for each band of image data. */ + int[] dataOffsets; + + /** Scanline stride of the image data contained in this Raster. */ + int scanlineStride; + + /** The image data array. */ + byte[][] data; + + /** A cached copy of minX + width for use in bounds checks. */ + private int maxX; + + /** A cached copy of minY + height for use in bounds checks. */ + private int maxY; + + /** + * Constructs a ByteBandedRaster with the given sampleModel. The + * Raster's upper left corner is origin and it is the same + * size as the SampleModel. A dataBuffer large + * enough to describe the Raster is automatically created. SampleModel + * must be of type BandedSampleModel. + * @param sampleModel The SampleModel that specifies the layout. + * @param origin The Point that specifies the origin. + */ + public ByteBandedRaster(SampleModel sampleModel, + Point origin) { + this(sampleModel, + sampleModel.createDataBuffer(), + new Rectangle(origin.x, + origin.y, + sampleModel.getWidth(), + sampleModel.getHeight()), + origin, + null); + } + + /** + * Constructs a ByteBanded Raster with the given sampleModel + * and DataBuffer. The Raster's upper left corner is origin and + * it is the same size as the SampleModel. The DataBuffer is not + * initialized and must be a DataBufferShort compatible with SampleModel. + * SampleModel must be of type BandedSampleModel. + * @param sampleModel The SampleModel that specifies the layout. + * @param dataBuffer The DataBufferShort that contains the image data. + * @param origin The Point that specifies the origin. + */ + public ByteBandedRaster(SampleModel sampleModel, + DataBuffer dataBuffer, + Point origin) { + this(sampleModel, dataBuffer, + new Rectangle(origin.x , origin.y, + sampleModel.getWidth(), + sampleModel.getHeight()), + origin, null); + } + + /** + * Constructs a ByteBandedRaster with the given sampleModel, + * DataBuffer, and parent. DataBuffer must be a DataBufferShort and + * SampleModel must be of type BandedSampleModel. + * When translated into the base Raster's + * coordinate system, aRegion must be contained by the base Raster. + * Origin is the coordinate in the new Raster's coordinate system of + * the origin of the base Raster. (The base Raster is the Raster's + * ancestor which has no parent.) + * + * Note that this constructor should generally be called by other + * constructors or create methods, it should not be used directly. + * @param sampleModel The SampleModel that specifies the layout. + * @param dataBuffer The DataBufferShort that contains the image data. + * @param aRegion The Rectangle that specifies the image area. + * @param origin The Point that specifies the origin. + * @param parent The parent (if any) of this raster. + */ + public ByteBandedRaster(SampleModel sampleModel, + DataBuffer dataBuffer, + Rectangle aRegion, + Point origin, + ByteBandedRaster parent) { + + super(sampleModel, dataBuffer, aRegion, origin, parent); + this.maxX = minX + width; + this.maxY = minY + height; + + if (!(dataBuffer instanceof DataBufferByte)) { + throw new RasterFormatException("ByteBandedRaster must have" + + "byte DataBuffers"); + } + DataBufferByte dbb = (DataBufferByte)dataBuffer; + + if (sampleModel instanceof BandedSampleModel) { + BandedSampleModel bsm = (BandedSampleModel)sampleModel; + this.scanlineStride = bsm.getScanlineStride(); + int bankIndices[] = bsm.getBankIndices(); + int bandOffsets[] = bsm.getBandOffsets(); + int dOffsets[] = dbb.getOffsets(); + dataOffsets = new int[bankIndices.length]; + data = new byte[bankIndices.length][]; + int xOffset = aRegion.x - origin.x; + int yOffset = aRegion.y - origin.y; + for (int i = 0; i < bankIndices.length; i++) { + data[i] = stealData(dbb, bankIndices[i]); + dataOffsets[i] = dOffsets[bankIndices[i]] + + xOffset + yOffset*scanlineStride + bandOffsets[i]; + } + } else { + throw new RasterFormatException("ByteBandedRasters must have"+ + "BandedSampleModels"); + } + verify(); + } + + + /** + * Returns a copy of the data offsets array. For each band the data + * offset is the index into the band's data array, of the first sample + * of the band. + */ + public int[] getDataOffsets() { + return (int[])dataOffsets.clone(); + } + + /** + * Returns data offset for the specified band. The data offset + * is the index into the band's data array + * in which the first sample of the first scanline is stored. + * @param The band whose offset is returned. + */ + public int getDataOffset(int band) { + return dataOffsets[band]; + } + + /** + * Returns the scanline stride -- the number of data array elements + * between a given sample and the sample in the same column + * of the next row in the same band. + */ + public int getScanlineStride() { + return scanlineStride; + } + + /** + * Returns the pixel stride, which is always equal to one for + * a Raster with a BandedSampleModel. + */ + public int getPixelStride() { + return 1; + } + + /** + * Returns a reference to the entire data array. + */ + public byte[][] getDataStorage() { + return data; + } + + /** + * Returns a reference to the specific band data array. + */ + public byte[] getDataStorage(int band) { + return data[band]; + } + + /** + * Returns the data elements for all bands at the specified + * location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param outData An object reference to an array of type defined by + * getTransferType() and length getNumDataElements(). + * If null an array of appropriate type and size will be + * allocated. + * @return An object reference to an array of type defined by + * getTransferType() with the request pixel data. + */ + public Object getDataElements(int x, int y, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x >= this.maxX) || (y >= this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + byte outData[]; + if (obj == null) { + outData = new byte[numDataElements]; + } else { + outData = (byte[])obj; + } + int off = (y-minY)*scanlineStride + (x-minX); + + for (int band = 0; band < numDataElements; band++) { + outData[band] = data[band][dataOffsets[band] + off]; + } + + return outData; + } + + /** + * Returns an array of data elements from the specified + * rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + *

+     *       byte[] bandData = (byte[])raster.getDataElement(x, y, w, h, null);
+     *       int numDataElements = raster.getNumDataElements();
+     *       byte[] pixel = new byte[numDataElements];
+     *       // To find a data element at location (x2, y2)
+     *       System.arraycopy(bandData, ((y2-y)*w + (x2-x))*numDataElements,
+     *                        pixel, 0, numDataElements);
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param outData An object reference to an array of type defined by + * getTransferType() and length w*h*getNumDataElements(). + * If null an array of appropriate type and size will be + * allocated. + * @return An object reference to an array of type defined by + * getTransferType() with the request pixel data. + */ + public Object getDataElements(int x, int y, int w, int h, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + byte outData[]; + if (obj == null) { + outData = new byte[numDataElements*w*h]; + } else { + outData = (byte[])obj; + } + int yoff = (y-minY)*scanlineStride + (x-minX); + + for (int c = 0; c < numDataElements; c++) { + int off = c; + byte[] bank = data[c]; + int dataOffset = dataOffsets[c]; + + int yoff2 = yoff; + for (int ystart=0; ystart < h; ystart++, yoff2 += scanlineStride) { + int xoff = dataOffset + yoff2; + for (int xstart=0; xstart < w; xstart++) { + outData[off] = bank[xoff++]; + off += numDataElements; + } + } + } + + return outData; + } + + /** + * Returns a byte array of data elements from the specified rectangular + * region for the specified band. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       byte[] bandData = raster.getByteData(x, y, w, h, null);
+     *       // To find the data element at location (x2, y2)
+     *       byte bandElement = bandData[((y2-y)*w + (x2-x))];
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param band The band to return. + * @param outData If non-null, data elements for all bands + * at the specified location are returned in this array. + * @return Data array with data elements for all bands. + */ + public byte[] getByteData(int x, int y, int w, int h, + int band, byte[] outData) { + // Bounds check for 'band' will be performed automatically + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + if (outData == null) { + outData = new byte[scanlineStride*h]; + } + int yoff = (y-minY)*scanlineStride + (x-minX) + dataOffsets[band]; + + if (scanlineStride == w) { + System.arraycopy(data[band], yoff, outData, 0, w*h); + } else { + int off = 0; + for (int ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + System.arraycopy(data[band], yoff, outData, off, w); + off += w; + } + } + + return outData; + } + + /** + * Returns a byte array of data elements from the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       byte[] bandData = raster.getByteData(x, y, w, h, null);
+     *       int numDataElements = raster.getNumDataElements();
+     *       byte[] pixel = new byte[numDataElements];
+     *       // To find a data element at location (x2, y2)
+     *       System.arraycopy(bandData, ((y2-y)*w + (x2-x))*numDataElements,
+     *                        pixel, 0, numDataElements);
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param outData If non-null, data elements for all bands + * at the specified location are returned in this array. + * @return Data array with data elements for all bands. + */ + public byte[] getByteData(int x, int y, int w, int h, byte[] outData) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + if (outData == null) { + outData = new byte[numDataElements*scanlineStride*h]; + } + int yoff = (y-minY)*scanlineStride + (x-minX); + + for (int c = 0; c < numDataElements; c++) { + int off = c; + byte[] bank = data[c]; + int dataOffset = dataOffsets[c]; + + // REMIND: Should keep track if dataoffsets are in a nice order + int yoff2 = yoff; + for (int ystart=0; ystart < h; ystart++, yoff2 += scanlineStride) { + int xoff = dataOffset + yoff2; + for (int xstart=0; xstart < w; xstart++) { + outData[off] = bank[xoff++]; + off += numDataElements; + } + } + } + + return outData; + } + + /** + * Stores the data elements for all bands at the specified location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param inData An object reference to an array of type defined by + * getTransferType() and length getNumDataElements() + * containing the pixel data to place at x,y. + */ + public void setDataElements(int x, int y, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x >= this.maxX) || (y >= this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + byte inData[] = (byte[])obj; + int off = (y-minY)*scanlineStride + (x-minX); + for (int i = 0; i < numDataElements; i++) { + data[i][dataOffsets[i] + off] = inData[i]; + } + markDirty(); + } + + /** + * Stores the Raster data at the specified location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param inRaster Raster of data to place at x,y location. + */ + public void setDataElements(int x, int y, Raster inRaster) { + int dstOffX = inRaster.getMinX() + x; + int dstOffY = inRaster.getMinY() + y; + int width = inRaster.getWidth(); + int height = inRaster.getHeight(); + if ((dstOffX < this.minX) || (dstOffY < this.minY) || + (dstOffX + width > this.maxX) || (dstOffY + height > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + + setDataElements(dstOffX, dstOffY, width, height, inRaster); + } + + /** + * Stores the Raster data at the specified location. + * @param dstX The absolute X coordinate of the destination pixel + * that will receive a copy of the upper-left pixel of the + * inRaster + * @param dstY The absolute Y coordinate of the destination pixel + * that will receive a copy of the upper-left pixel of the + * inRaster + * @param width The number of pixels to store horizontally + * @param height The number of pixels to store vertically + * @param inRaster Raster of data to place at x,y location. + */ + private void setDataElements(int dstX, int dstY, + int width, int height, + Raster inRaster) { + // Assume bounds checking has been performed previously + if (width <= 0 || height <= 0) { + return; + } + + int srcOffX = inRaster.getMinX(); + int srcOffY = inRaster.getMinY(); + Object tdata = null; + +// // REMIND: Do something faster! +// if (inRaster instanceof ByteBandedRaster) { +// } + + for (int startY=0; startY < height; startY++) { + // Grab one scanline at a time + tdata = inRaster.getDataElements(srcOffX, srcOffY+startY, + width, 1, tdata); + setDataElements(dstX, dstY+startY, width, 1, tdata); + } + } + + /** + * Stores an array of data elements into the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param inData An object reference to an array of type defined by + * getTransferType() and length w*h*getNumDataElements() + * containing the pixel data to place between x,y and + * x+h, y+h. + */ + public void setDataElements(int x, int y, int w, int h, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + byte inData[] = (byte[])obj; + int yoff = (y-minY)*scanlineStride + (x-minX); + + for (int c = 0; c < numDataElements; c++) { + int off = c; + byte[] bank = data[c]; + int dataOffset = dataOffsets[c]; + + int yoff2 = yoff; + for (int ystart=0; ystart < h; ystart++, yoff2 += scanlineStride) { + int xoff = dataOffset + yoff2; + for (int xstart=0; xstart < w; xstart++) { + bank[xoff++] = inData[off]; + off += numDataElements; + } + } + } + + markDirty(); + } + + /** + * Stores a byte array of data elements into the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param band The band to set. + * @param inData The data elements to be stored. + */ + public void putByteData(int x, int y, int w, int h, + int band, byte[] inData) { + // Bounds check for 'band' will be performed automatically + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + int yoff = (y-minY)*scanlineStride + (x-minX) + dataOffsets[band]; + int xoff; + int off = 0; + int xstart; + int ystart; + + if (scanlineStride == w) { + System.arraycopy(inData, 0, data[band], yoff, w*h); + } else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + System.arraycopy(inData, off, data[band], yoff, w); + off += w; + } + } + + markDirty(); + } + + /** + * Stores a byte array of data elements into the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param inData The data elements to be stored. + */ + public void putByteData(int x, int y, int w, int h, byte[] inData) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + int yoff = (y-minY)*scanlineStride + (x-minX); + + for (int c = 0; c < numDataElements; c++) { + int off = c; + byte[] bank = data[c]; + int dataOffset = dataOffsets[c]; + + int yoff2 = yoff; + for (int ystart=0; ystart < h; ystart++, yoff2 += scanlineStride) { + int xoff = dataOffset + yoff2; + for (int xstart=0; xstart < w; xstart++) { + bank[xoff++] = inData[off]; + off += numDataElements; + } + } + } + + markDirty(); + } + + /** + * Creates a Writable subraster given a region of the raster. The x and y + * coordinates specify the horizontal and vertical offsets + * from the upper-left corner of this raster to the upper-left corner + * of the subraster. A subset of the bands of the parent Raster may + * be specified. If this is null, then all the bands are present in the + * subRaster. A translation to the subRaster may also be specified. + * Note that the subraster will reference the same + * DataBuffers as the parent raster, but using different offsets. + * @param x X offset. + * @param y Y offset. + * @param width Width of the subraster. + * @param height Height of the subraster. + * @param x0 Translated X origin of the subraster. + * @param y0 Translated Y origin of the subraster. + * @param bandList Array of band indices. + * @exception RasterFormatException + * if the specified bounding box is outside of the parent raster. + */ + public WritableRaster createWritableChild (int x, int y, + int width, int height, + int x0, int y0, + int bandList[]) { + + if (x < this.minX) { + throw new RasterFormatException("x lies outside raster"); + } + if (y < this.minY) { + throw new RasterFormatException("y lies outside raster"); + } + if ((x+width < x) || (x+width > this.width + this.minX)) { + throw new RasterFormatException("(x + width) is outside raster") ; + } + if ((y+height < y) || (y+height > this.height + this.minY)) { + throw new RasterFormatException("(y + height) is outside raster"); + } + + SampleModel sm; + + if (bandList != null) + sm = sampleModel.createSubsetSampleModel(bandList); + else + sm = sampleModel; + + int deltaX = x0 - x; + int deltaY = y0 - y; + + return new ByteBandedRaster(sm, + dataBuffer, + new Rectangle(x0,y0,width,height), + new Point(sampleModelTranslateX+deltaX, + sampleModelTranslateY+deltaY), + this); + } + + /** + * Creates a subraster given a region of the raster. The x and y + * coordinates specify the horizontal and vertical offsets + * from the upper-left corner of this raster to the upper-left corner + * of the subraster. A subset of the bands of the parent Raster may + * be specified. If this is null, then all the bands are present in the + * subRaster. A translation to the subRaster may also be specified. + * Note that the subraster will reference the same + * DataBuffers as the parent raster, but using different offsets. + * @param x X offset. + * @param y Y offset. + * @param width Width (in pixels) of the subraster. + * @param height Height (in pixels) of the subraster. + * @param x0 Translated X origin of the subraster. + * @param y0 Translated Y origin of the subraster. + * @param bandList Array of band indices. + * @exception RasterFormatException + * if the specified bounding box is outside of the parent raster. + */ + public Raster createChild (int x, int y, + int width, int height, + int x0, int y0, + int bandList[]) { + return createWritableChild(x, y, width, height, x0, y0, bandList); + } + + /** + * Creates a Raster with the same layout but using a different + * width and height, and with new zeroed data arrays. + */ + public WritableRaster createCompatibleWritableRaster(int w, int h) { + if (w <= 0 || h <=0) { + throw new RasterFormatException("negative "+ + ((w <= 0) ? "width" : "height")); + } + + SampleModel sm = sampleModel.createCompatibleSampleModel(w,h); + + return new ByteBandedRaster(sm, new Point(0,0)); + } + + /** + * Creates a Raster with the same layout and the same + * width and height, and with new zeroed data arrays. If + * the Raster is a subRaster, this will call + * createCompatibleRaster(width, height). + */ + public WritableRaster createCompatibleWritableRaster() { + return createCompatibleWritableRaster(width, height); + } + + /** + * Verify that the layout parameters are consistent with the data. + * Verifies whether the data buffer has enough data for the raster, + * taking into account offsets, after ensuring all offsets are >=0. + * @throws RasterFormatException if a problem is detected. + */ + private void verify() { + + /* Need to re-verify the dimensions since a sample model may be + * specified to the constructor + */ + if (width <= 0 || height <= 0 || + height > (Integer.MAX_VALUE / width)) + { + throw new RasterFormatException("Invalid raster dimension"); + } + + if (scanlineStride < 0 || + scanlineStride > (Integer.MAX_VALUE / height)) + { + // integer overflow + throw new RasterFormatException("Incorrect scanline stride: " + + scanlineStride); + } + + if ((long)minX - sampleModelTranslateX < 0 || + (long)minY - sampleModelTranslateY < 0) { + + throw new RasterFormatException("Incorrect origin/translate: (" + + minX + ", " + minY + ") / (" + + sampleModelTranslateX + ", " + sampleModelTranslateY + ")"); + } + + + if (height > 1 || minY - sampleModelTranslateY > 0) { + // buffer should contain at least one scanline + for (int i = 0; i < data.length; i++) { + if (scanlineStride > data[i].length) { + throw new RasterFormatException("Incorrect scanline stride: " + + scanlineStride); + } + } + } + + // Make sure data for Raster is in a legal range + for (int i=0; i < dataOffsets.length; i++) { + if (dataOffsets[i] < 0) { + throw new RasterFormatException("Data offsets for band "+i+ + "("+dataOffsets[i]+ + ") must be >= 0"); + } + } + + int lastScanOffset = (height - 1) * scanlineStride; + + if ((width - 1) > (Integer.MAX_VALUE - lastScanOffset)) { + throw new RasterFormatException("Invalid raster dimension"); + } + int lastPixelOffset = lastScanOffset + (width-1); + + int maxIndex = 0; + int index; + + for (int i=0; i < numDataElements; i++) { + if (dataOffsets[i] > (Integer.MAX_VALUE - lastPixelOffset)) { + throw new RasterFormatException("Invalid raster dimension"); + } + index = lastPixelOffset + dataOffsets[i]; + if (index > maxIndex) { + maxIndex = index; + } + } + + if (data.length == 1) { + if (data[0].length <= maxIndex*numDataElements) { + throw new RasterFormatException("Data array too small "+ + "(it is "+data[0].length+ + " and should be > "+ + (maxIndex*numDataElements)+ + " )"); + } + } + else { + for (int i=0; i < numDataElements; i++) { + if (data[i].length <= maxIndex) { + throw new RasterFormatException("Data array too small "+ + "(it is "+data[i].length+ + " and should be > "+ + maxIndex+" )"); + } + } + } + } + + public String toString() { + return new String ("ByteBandedRaster: width = "+width+" height = " + + height + +" #bands "+numDataElements + +" minX = "+minX+" minY = "+minY); + } + +} diff --git a/sources/net.sf.j2s.java.core/src/sun/awt/image/DataStealer.java b/sources/net.sf.j2s.java.core/src/sun/awt/image/DataStealer.java index 12482daef..79c53427c 100644 --- a/sources/net.sf.j2s.java.core/src/sun/awt/image/DataStealer.java +++ b/sources/net.sf.j2s.java.core/src/sun/awt/image/DataStealer.java @@ -3,11 +3,13 @@ import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferUShort; + import sun.java2d.StateTrackableDelegate; public interface DataStealer { public byte[] getData(DataBufferByte dbb, int bank); -// public short[] getData(DataBufferUShort dbus, int bank); + public short[] getData(DataBufferUShort dbus, int bank); public int[] getData(DataBufferInt dbi, int bank); public StateTrackableDelegate getTrackable(DataBuffer db); public void setTrackable(DataBuffer db, StateTrackableDelegate trackable); diff --git a/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortBandedRaster.java b/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortBandedRaster.java new file mode 100644 index 000000000..c73e7a72e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortBandedRaster.java @@ -0,0 +1,818 @@ +/* + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.awt.image; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.RasterFormatException; +import java.awt.image.SampleModel; +import java.awt.image.BandedSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferUShort; +import java.awt.Rectangle; +import java.awt.Point; + +/** + * This class defines a Raster with pixels consisting of multiple 16-bit + * samples stored in separate arrays for each band. Operations on + * sets of pixels are performed on a given band of each pixel + * in the set before moving on to the next band. The arrays used + * for storage may be distinct or shared between some or all of + * the bands. + * There is only one pixel stride and one scanline stride for all + * bands. This type of Raster can be used with a + * ComponentColorModel. This class requires a BandedSampleModel. + * + */ +public class ShortBandedRaster extends SunWritableRaster { + + /** Data offsets for each band of image data. */ + int[] dataOffsets; + + /** Scanline stride of the image data contained in this Raster. */ + int scanlineStride; + + /** The image data array. */ + short[][] data; + + /** A cached copy of minX + width for use in bounds checks. */ + private int maxX; + + /** A cached copy of minY + height for use in bounds checks. */ + private int maxY; + + /** + * Constructs a ShortBandedRaster with the given SampleModel. + * The Raster's upper left corner is origin and it is the same + * size as the SampleModel. A DataBuffer large enough to describe the + * Raster is automatically created. SampleModel must be of type + * BandedSampleModel. + * @param sampleModel The SampleModel that specifies the layout. + * @param origin The Point that specified the origin. + */ + public ShortBandedRaster(SampleModel sampleModel, + Point origin) { + this(sampleModel, + sampleModel.createDataBuffer(), + new Rectangle(origin.x, + origin.y, + sampleModel.getWidth(), + sampleModel.getHeight()), + origin, + null); + } + + /** + * Constructs a ShortBandedRaster with the given SampleModel + * and DataBuffer. The Raster's upper left corner is origin and + * it is the same size as the SampleModel. The DataBuffer is not + * initialized and must be a DataBufferUShort compatible with SampleModel. + * SampleModel must be of type BandedSampleModel. + * @param sampleModel The SampleModel that specifies the layout. + * @param dataBuffer The DataBufferUShort that contains the image data. + * @param origin The Point that specifies the origin. + */ + public ShortBandedRaster(SampleModel sampleModel, + DataBuffer dataBuffer, + Point origin) { + this(sampleModel, dataBuffer, + new Rectangle(origin.x, origin.y, + sampleModel.getWidth(), + sampleModel.getHeight()), + origin, null); + } + + /** + * Constructs a ShortBandedRaster with the given SampleModel, + * DataBuffer, and parent. DataBuffer must be a DataBufferUShort and + * SampleModel must be of type BandedSampleModel. + * When translated into the base Raster's + * coordinate system, aRegion must be contained by the base Raster. + * Origin is the coordinate in the new Raster's coordinate system of + * the origin of the base Raster. (The base Raster is the Raster's + * ancestor which has no parent.) + * + * Note that this constructor should generally be called by other + * constructors or create methods, it should not be used directly. + * @param sampleModel The SampleModel that specifies the layout. + * @param dataBuffer The DataBufferUShort that contains the image data. + * @param aRegion The Rectangle that specifies the image area. + * @param origin The Point that specifies the origin. + * @param parent The parent (if any) of this raster. + */ + public ShortBandedRaster(SampleModel sampleModel, + DataBuffer dataBuffer, + Rectangle aRegion, + Point origin, + ShortBandedRaster parent) { + + super(sampleModel, dataBuffer, aRegion, origin, parent); + this.maxX = minX + width; + this.maxY = minY + height; + if (!(dataBuffer instanceof DataBufferUShort)) { + throw new RasterFormatException("ShortBandedRaster must have " + + "ushort DataBuffers"); + } + DataBufferUShort dbus = (DataBufferUShort)dataBuffer; + + if (sampleModel instanceof BandedSampleModel) { + BandedSampleModel bsm = (BandedSampleModel)sampleModel; + this.scanlineStride = bsm.getScanlineStride(); + int bankIndices[] = bsm.getBankIndices(); + int bandOffsets[] = bsm.getBandOffsets(); + int dOffsets[] = dbus.getOffsets(); + dataOffsets = new int[bankIndices.length]; + data = new short[bankIndices.length][]; + int xOffset = aRegion.x - origin.x; + int yOffset = aRegion.y - origin.y; + for (int i = 0; i < bankIndices.length; i++) { + data[i] = stealData(dbus, bankIndices[i]); + dataOffsets[i] = dOffsets[bankIndices[i]] + + xOffset + yOffset*scanlineStride + bandOffsets[i]; + } + } else { + throw new RasterFormatException("ShortBandedRasters must have "+ + "BandedSampleModels"); + } + verify(); + } + + /** + * Returns a copy of the data offsets array. For each band the data offset + * is the index into the band's data array, of the first sample of the + * band. + */ + public int[] getDataOffsets() { + return (int[]) dataOffsets.clone(); + } + + /** + * Returns the data offset for the specified band. The data offset + * is the index into the band's data array + * in which the first sample of the first scanline is stored. + * @param The band whose offset is returned. + */ + public int getDataOffset(int band) { + return dataOffsets[band]; + } + + /** + * Returns the scanline stride -- the number of data array elements between + * a given sample and the sample in the same column + * of the next row in the same band. + */ + public int getScanlineStride() { + return scanlineStride; + } + + /** + * Returns the pixel stride, which is always equal to one for + * a Raster with a BandedSampleModel. + */ + public int getPixelStride() { + return 1; + } + + /** + * Returns a reference to the entire data array. + */ + public short[][] getDataStorage() { + return data; + } + + /** + * Returns a reference to the specific band data array. + */ + public short[] getDataStorage(int band) { + return data[band]; + } + + /** + * Returns the data elements for all bands at the specified + * location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param outData An object reference to an array of type defined by + * getTransferType() and length getNumDataElements(). + * If null an array of appropriate type and size will be + * allocated. + * @return An object reference to an array of type defined by + * getTransferType() with the request pixel data. + */ + public Object getDataElements(int x, int y, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x >= this.maxX) || (y >= this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short outData[]; + if (obj == null) { + outData = new short[numDataElements]; + } else { + outData = (short[])obj; + } + + int off = (y-minY)*scanlineStride + (x-minX); + + for (int band = 0; band < numDataElements; band++) { + outData[band] = data[band][dataOffsets[band] + off]; + } + + return outData; + } + + /** + * Returns an array of data elements from the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       short[] bandData = (short[])Raster.getDataElements(x, y, w, h, null);
+     *       int numDataElements = Raster.getnumDataElements();
+     *       short[] pixel = new short[numDataElements];
+     *       // To find a data element at location (x2, y2)
+     *       System.arraycopy(bandData, ((y2-y)*w + (x2-x))*numDataElements,
+     *                        pixel, 0, numDataElements);
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param outData An object reference to an array of type defined by + * getTransferType() and length w*h*getNumDataElements(). + * If null an array of appropriate type and size will be + * allocated. + * @return An object reference to an array of type defined by + * getTransferType() with the request pixel data. + */ + public Object getDataElements(int x, int y, int w, int h, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short outData[]; + if (obj == null) { + outData = new short[numDataElements*w*h]; + } else { + outData = (short[])obj; + } + int yoff = (y-minY)*scanlineStride + (x-minX); + + for (int c = 0; c < numDataElements; c++) { + int off = c; + short[] bank = data[c]; + int dataOffset = dataOffsets[c]; + + int yoff2 = yoff; + for (int ystart=0; ystart < h; ystart++, yoff2 += scanlineStride) { + int xoff = dataOffset + yoff2; + for (int xstart=0; xstart < w; xstart++) { + outData[off] = bank[xoff++]; + off += numDataElements; + } + } + } + + return outData; + } + + /** + * Returns a short array of data elements from the specified rectangular + * region for the specified band. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       short[] bandData = Raster.getShortData(x, y, w, h, null);
+     *       // To find the data element at location (x2, y2)
+     *       short bandElement = bandData[((y2-y)*w + (x2-x))];
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param band The band to return. + * @param outData If non-null, data elements for all bands + * at the specified location are returned in this array. + * @return Data array with data elements for all bands. + */ + public short[] getShortData(int x, int y, int w, int h, + int band, short[] outData) { + // Bounds check for 'band' will be performed automatically + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + if (outData == null) { + outData = new short[scanlineStride*h]; + } + int yoff = (y-minY)*scanlineStride + (x-minX) + dataOffsets[band]; + + if (scanlineStride == w) { + System.arraycopy(data[band], yoff, outData, 0, w*h); + } else { + int off = 0; + for (int ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + System.arraycopy(data[band], yoff, outData, off, w); + off += w; + } + } + + return outData; + } + + /** + * Returns a short array of data elements from the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       short[] bandData = Raster.getShortData(x, y, w, h, null);
+     *       int numDataElements = Raster.getnumDataElements();
+     *       short[] pixel = new short[numDataElements];
+     *       // To find a data element at location (x2, y2)
+     *       System.arraycopy(bandData, ((y2-y)*w + (x2-x))*numDataElements,
+     *                        pixel, 0, numDataElements);
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param outData If non-null, data elements for all bands + * at the specified location are returned in this array. + * @return Data array with data elements for all bands. + */ + public short[] getShortData(int x, int y, int w, int h, + short[] outData) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + if (outData == null) { + outData = new short[numDataElements*scanlineStride*h]; + } + int yoff = (y-minY)*scanlineStride + (x-minX); + + for (int c = 0; c < numDataElements; c++) { + int off = c; + short[] bank = data[c]; + int dataOffset = dataOffsets[c]; + + int yoff2 = yoff; + for (int ystart=0; ystart < h; ystart++, yoff2 += scanlineStride) { + int xoff = dataOffset + yoff2; + for (int xstart=0; xstart < w; xstart++) { + outData[off] = bank[xoff++]; + off += numDataElements; + } + } + } + + return outData; + } + + /** + * Stores the data element for all bands at the specified location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param inData An object reference to an array of type defined by + * getTransferType() and length getNumDataElements() + * containing the pixel data to place at x,y. + */ + public void setDataElements(int x, int y, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x >= this.maxX) || (y >= this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short inData[] = (short[])obj; + int off = (y-minY)*scanlineStride + (x-minX); + for (int i = 0; i < numDataElements; i++) { + data[i][dataOffsets[i] + off] = inData[i]; + } + + markDirty(); + } + + /** + * Stores the Raster data at the specified location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param inRaster Raster of data to place at x,y location. + */ + public void setDataElements(int x, int y, Raster inRaster) { + int dstOffX = x + inRaster.getMinX(); + int dstOffY = y + inRaster.getMinY(); + int width = inRaster.getWidth(); + int height = inRaster.getHeight(); + if ((dstOffX < this.minX) || (dstOffY < this.minY) || + (dstOffX + width > this.maxX) || (dstOffY + height > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + + setDataElements(dstOffX, dstOffY, width, height, inRaster); + } + + /** + * Stores the Raster data at the specified location. + * @param dstX The absolute X coordinate of the destination pixel + * that will receive a copy of the upper-left pixel of the + * inRaster + * @param dstY The absolute Y coordinate of the destination pixel + * that will receive a copy of the upper-left pixel of the + * inRaster + * @param width The number of pixels to store horizontally + * @param height The number of pixels to store vertically + * @param inRaster Raster of data to place at x,y location. + */ + private void setDataElements(int dstX, int dstY, + int width, int height, + Raster inRaster) { + // Assume bounds checking has been performed previously + if (width <= 0 || height <= 0) { + return; + } + + // Write inRaster (minX, minY) to (dstX, dstY) + + int srcOffX = inRaster.getMinX(); + int srcOffY = inRaster.getMinY(); + Object tdata = null; + +// // REMIND: Do something faster! +// if (inRaster instanceof ShortBandedRaster) { +// } + + for (int startY=0; startY < height; startY++) { + // Grab one scanline at a time + tdata = inRaster.getDataElements(srcOffX, srcOffY+startY, + width, 1, tdata); + setDataElements(dstX, dstY + startY, width, 1, tdata); + } + } + + /** + * Stores an array of data elements into the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param inData An object reference to an array of type defined by + * getTransferType() and length w*h*getNumDataElements() + * containing the pixel data to place between x,y and + * x+h, y+h. + */ + public void setDataElements(int x, int y, int w, int h, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short inData[] = (short[])obj; + int yoff = (y-minY)*scanlineStride + (x-minX); + + for (int c = 0; c < numDataElements; c++) { + int off = c; + short[] bank = data[c]; + int dataOffset = dataOffsets[c]; + + int yoff2 = yoff; + for (int ystart=0; ystart < h; ystart++, yoff2 += scanlineStride) { + int xoff = dataOffset + yoff2; + for (int xstart=0; xstart < w; xstart++) { + bank[xoff++] = inData[off]; + off += numDataElements; + } + } + } + + markDirty(); + } + + /** + * Stores a short array of data elements into the specified + * rectangular region for the specified band. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param band The band to set. + * @param inData The data elements to be stored. + */ + public void putShortData(int x, int y, int w, int h, + int band, short[] inData) { + // Bounds check for 'band' will be performed automatically + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + int yoff = (y-minY)*scanlineStride + (x-minX) + dataOffsets[band]; + int xoff; + int off = 0; + int xstart; + int ystart; + + if (scanlineStride == w) { + System.arraycopy(inData, 0, data[band], yoff, w*h); + } else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + System.arraycopy(inData, off, data[band], yoff, w); + off += w; + } + } + + markDirty(); + } + + /** + * Stores a short integer array of data elements into the specified + * rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param inData The data elements to be stored. + */ + public void putShortData(int x, int y, int w, int h, short[] inData) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + int yoff = (y-minY)*scanlineStride + (x-minX); + + for (int c = 0; c < numDataElements; c++) { + int off = c; + short[] bank = data[c]; + int dataOffset = dataOffsets[c]; + + int yoff2 = yoff; + for (int ystart=0; ystart < h; ystart++, yoff2 += scanlineStride) { + int xoff = dataOffset + yoff2; + for (int xstart=0; xstart < w; xstart++) { + bank[xoff++] = inData[off]; + off += numDataElements; + } + } + } + + markDirty(); + } + + /** + * Creates a Writable subRaster given a region of the Raster. The x and y + * coordinates specify the horizontal and vertical offsets + * from the upper-left corner of this Raster to the upper-left corner + * of the subRaster. A subset of the bands of the parent Raster may + * be specified. If this is null, then all the bands are present in the + * subRaster. A translation to the subRaster may also be specified. + * Note that the subRaster will reference the same + * DataBuffers as the parent Raster, but using different offsets. + * @param x X offset. + * @param y Y offset. + * @param width Width (in pixels) of the subraster. + * @param height Height (in pixels) of the subraster. + * @param x0 Translated X origin of the subraster. + * @param y0 Translated Y origin of the subraster. + * @param bandList Array of band indices. + * @exception RasterFormatException + * if the specified bounding box is outside of the parent Raster. + */ + public WritableRaster createWritableChild(int x, int y, + int width, int height, + int x0, int y0, + int bandList[]) { + + if (x < this.minX) { + throw new RasterFormatException("x lies outside raster"); + } + if (y < this.minY) { + throw new RasterFormatException("y lies outside raster"); + } + if ((x+width < x) || (x+width > this.minX + this.width)) { + throw new RasterFormatException("(x + width) is outside of Raster"); + } + if ((y+height < y) || (y+height > this.minY + this.height)) { + throw new RasterFormatException("(y + height) is outside of Raster"); + } + + SampleModel sm; + + if (bandList != null) + sm = sampleModel.createSubsetSampleModel(bandList); + else + sm = sampleModel; + + int deltaX = x0 - x; + int deltaY = y0 - y; + + return new ShortBandedRaster(sm, + dataBuffer, + new Rectangle(x0, y0, width, height), + new Point(sampleModelTranslateX+deltaX, + sampleModelTranslateY+deltaY), + this); + + } + + /** + * Creates a subraster given a region of the raster. The x and y + * coordinates specify the horizontal and vertical offsets + * from the upper-left corner of this raster to the upper-left corner + * of the subraster. A subset of the bands of the parent Raster may + * be specified. If this is null, then all the bands are present in the + * subRaster. A translation to the subRaster may also be specified. + * Note that the subraster will reference the same + * DataBuffers as the parent raster, but using different offsets. + * @param x X offset. + * @param y Y offset. + * @param width Width (in pixels) of the subraster. + * @param height Height (in pixels) of the subraster. + * @param x0 Translated X origin of the subraster. + * @param y0 Translated Y origin of the subraster. + * @param bandList Array of band indices. + * @exception RasterFormatException + * if the specified bounding box is outside of the parent raster. + */ + public Raster createChild (int x, int y, + int width, int height, + int x0, int y0, + int bandList[]) { + return createWritableChild(x, y, width, height, x0, y0, bandList); + } + + /** + * Creates a Raster with the same layout but using a different + * width and height, and with new zeroed data arrays. + */ + public WritableRaster createCompatibleWritableRaster(int w, int h) { + if (w <= 0 || h <=0) { + throw new RasterFormatException("negative "+ + ((w <= 0) ? "width" : "height")); + } + + SampleModel sm = sampleModel.createCompatibleSampleModel(w,h); + + return new ShortBandedRaster(sm, new Point(0,0)); + } + + /** + * Creates a Raster with the same layout and the same + * width and height, and with new zeroed data arrays. If + * the Raster is a subRaster, this will call + * createCompatibleRaster(width, height). + */ + public WritableRaster createCompatibleWritableRaster() { + return createCompatibleWritableRaster(width,height); + } + + /** + * Verify that the layout parameters are consistent with the data. + * Verifies whether the data buffer has enough data for the raster, + * taking into account offsets, after ensuring all offsets are >=0. + * @throws RasterFormatException if a problem is detected. + */ + private void verify() { + + /* Need to re-verify the dimensions since a sample model may be + * specified to the constructor + */ + if (width <= 0 || height <= 0 || + height > (Integer.MAX_VALUE / width)) + { + throw new RasterFormatException("Invalid raster dimension"); + } + + if (scanlineStride < 0 || + scanlineStride > (Integer.MAX_VALUE / height)) + { + // integer overflow + throw new RasterFormatException("Incorrect scanline stride: " + + scanlineStride); + } + + if ((long)minX - sampleModelTranslateX < 0 || + (long)minY - sampleModelTranslateY < 0) { + + throw new RasterFormatException("Incorrect origin/translate: (" + + minX + ", " + minY + ") / (" + + sampleModelTranslateX + ", " + sampleModelTranslateY + ")"); + } + + if (height > 1 || minY - sampleModelTranslateY > 0) { + // buffer should contain at least one scanline + for (int i = 0; i < data.length; i++) { + if (scanlineStride > data[i].length) { + throw new RasterFormatException("Incorrect scanline stride: " + + scanlineStride); + } + } + } + + // Make sure data for Raster is in a legal range + for (int i=0; i < dataOffsets.length; i++) { + if (dataOffsets[i] < 0) { + throw new RasterFormatException("Data offsets for band "+i+ + "("+dataOffsets[i]+ + ") must be >= 0"); + } + } + + int lastScanOffset = (height - 1) * scanlineStride; + if ((width - 1) > (Integer.MAX_VALUE - lastScanOffset)) { + throw new RasterFormatException("Invalid raster dimension"); + } + int lastPixelOffset = lastScanOffset + (width - 1); + + int maxIndex = 0; + int index; + + for (int i=0; i < numDataElements; i++) { + if (dataOffsets[i] > (Integer.MAX_VALUE - lastPixelOffset)) { + throw new RasterFormatException("Invalid raster dimension"); + } + index = lastPixelOffset + dataOffsets[i]; + if (index > maxIndex) { + maxIndex = index; + } + } + for (int i=0; i < numDataElements; i++) { + if (data[i].length <= maxIndex) { + throw new RasterFormatException("Data array too small " + + "(should be > "+ maxIndex+" )"); + } + } + } + + public String toString() { + return new String ("ShortBandedRaster: width = "+width+" height = " + + height + +" #numBands " + numBands + +" #dataElements "+numDataElements); + + } + +} diff --git a/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortComponentRaster.java b/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortComponentRaster.java new file mode 100644 index 000000000..cdbc93c7e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortComponentRaster.java @@ -0,0 +1,892 @@ +/* + * Copyright (c) 1997, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.awt.image; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.RasterFormatException; +import java.awt.image.SampleModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferUShort; +import java.awt.Rectangle; +import java.awt.Point; + +/** + * This class defines a Raster with pixels consisting of one or more 16-bit + * data elements stored in close proximity to each other in a short integer + * array. The bit precision per data element is that + * of the data type (that is, the bit precision for this Raster is 16). + * There is only one pixel stride and one scanline stride for all + * bands. This type of Raster can be used with a + * ComponentColorModel if there are multiple bands, or a + * IndexColorModel if there is only one band. + *

+ * For example, 5-6-5 RGB image data can be represented by a + * ShortComponentRaster using a SinglePixelPackedSampleModel and + * a ComponentColorModel. + * + * + */ +public class ShortComponentRaster extends SunWritableRaster { + + /** private band offset for use by native code */ + protected int bandOffset; + + /** Data offsets for each band of image data. */ + protected int[] dataOffsets; + + /** Scanline stride of the image data contained in this Raster. */ + protected int scanlineStride; + + /** Pixel stride of the image data contained in this Raster. */ + protected int pixelStride; + + /** The image data array. */ + protected short[] data; + + int type; + + /** A cached copy of minX + width for use in bounds checks. */ + private int maxX; + + /** A cached copy of minY + height for use in bounds checks. */ + private int maxY; + + static private native void initIDs(); + static { + /* ensure that the necessary native libraries are loaded */ + NativeLibLoader.loadLibraries(); + initIDs(); + } + + /** + * Constructs a ShortComponentRaster with the given SampleModel. + * The Raster's upper left corner is origin and it is the same + * size as the SampleModel. A DataBuffer large enough to describe the + * Raster is automatically created. SampleModel must be of type + * ComponentSampleModel or SinglePixelPackedSampleModel. + * @param sampleModel The SampleModel that specifies the layout. + * @param origin The Point that specified the origin. + */ + public ShortComponentRaster(SampleModel sampleModel, Point origin) { + this(sampleModel, + sampleModel.createDataBuffer(), + new Rectangle(origin.x, + origin.y, + sampleModel.getWidth(), + sampleModel.getHeight()), + origin, + null); + } + + /** + * Constructs a ShortComponentRaster with the given SampleModel + * and DataBuffer. The Raster's upper left corner is origin and + * it is the same sizes the SampleModel. The DataBuffer is not + * initialized and must be a DataBufferUShort compatible with SampleModel. + * SampleModel must be of type ComponentSampleModel or + * SinglePixelPackedSampleModel. + * @param sampleModel The SampleModel that specifies the layout. + * @param dataBuffer The DataBufferUShort that contains the image data. + * @param origin The Point that specifies the origin. + */ + public ShortComponentRaster(SampleModel sampleModel, + DataBuffer dataBuffer, + Point origin) { + this(sampleModel, + dataBuffer, + new Rectangle(origin.x, + origin.y, + sampleModel.getWidth(), + sampleModel.getHeight()), + origin, + null); + } + + /** + * Constructs a ShortComponentRaster with the given SampleModel, + * DataBuffer, and parent. DataBuffer must be a DataBufferUShort and + * SampleModel must be of type ComponentSampleModel or + * SinglePixelPackedSampleModel. When translated into the base Raster's + * coordinate system, aRegion must be contained by the base Raster. + * Origin is the coodinate in the new Raster's coordinate system of + * the origin of the base Raster. (The base Raster is the Raster's + * ancestor which has no parent.) + * + * Note that this constructor should generally be called by other + * constructors or create methods, it should not be used directly. + * @param sampleModel The SampleModel that specifies the layout. + * @param dataBuffer The DataBufferUShort that contains the image data. + * @param aRegion The Rectangle that specifies the image area. + * @param origin The Point that specifies the origin. + * @param parent The parent (if any) of this raster. + */ + public ShortComponentRaster(SampleModel sampleModel, + DataBuffer dataBuffer, + Rectangle aRegion, + Point origin, + ShortComponentRaster parent) { + + super(sampleModel, dataBuffer, aRegion, origin, parent); + this.maxX = minX + width; + this.maxY = minY + height; + + if(!(dataBuffer instanceof DataBufferUShort)) { + throw new RasterFormatException("ShortComponentRasters must have "+ + "short DataBuffers"); + } + + DataBufferUShort dbus = (DataBufferUShort)dataBuffer; + this.data = stealData(dbus, 0); + if (dbus.getNumBanks() != 1) { + throw new + RasterFormatException("DataBuffer for ShortComponentRasters"+ + " must only have 1 bank."); + } + int dbOffset = dbus.getOffset(); + + if (sampleModel instanceof ComponentSampleModel) { + ComponentSampleModel csm = (ComponentSampleModel)sampleModel; + this.type = IntegerComponentRaster.TYPE_USHORT_SAMPLES; + this.scanlineStride = csm.getScanlineStride(); + this.pixelStride = csm.getPixelStride(); + this.dataOffsets = csm.getBandOffsets(); + int xOffset = aRegion.x - origin.x; + int yOffset = aRegion.y - origin.y; + for (int i = 0; i < getNumDataElements(); i++) { + dataOffsets[i] += dbOffset + + xOffset*pixelStride+yOffset*scanlineStride; + } + } else if (sampleModel instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = + (SinglePixelPackedSampleModel)sampleModel; + this.type = IntegerComponentRaster.TYPE_USHORT_PACKED_SAMPLES; + this.scanlineStride = sppsm.getScanlineStride(); + this.pixelStride = 1; + this.dataOffsets = new int[1]; + this.dataOffsets[0] = dbOffset; + int xOffset = aRegion.x - origin.x; + int yOffset = aRegion.y - origin.y; + dataOffsets[0] += xOffset+yOffset*scanlineStride; + } else { + throw new RasterFormatException("ShortComponentRasters must have"+ + "ComponentSampleModel or SinglePixelPackedSampleModel"); + } + this.bandOffset = this.dataOffsets[0]; + + verify(); + } + + /** + * Returns a copy of the data offsets array. For each band the data offset + * is the index into the band's data array, of the first sample of the + * band. + */ + public int[] getDataOffsets() { + return (int[]) dataOffsets.clone(); + } + + /** + * Returns the data offset for the specified band. The data offset + * is the index into the data array in which the first sample + * of the first scanline is stored. + * @param band The band whose offset is returned. + */ + public int getDataOffset(int band) { + return dataOffsets[band]; + } + + /** + * Returns the scanline stride -- the number of data array elements between + * a given sample and the same sample in the same column of the next row. + */ + public int getScanlineStride() { + return scanlineStride; + } + + /** + * Returns pixel stride -- the number of data array elements between two + * samples for the same band on the same scanline. + */ + public int getPixelStride() { + return pixelStride; + } + + /** + * Returns a reference to the data array. + */ + public short[] getDataStorage() { + return data; + } + + /** + * Returns the data elements for all bands at the specified + * location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param outData An object reference to an array of type defined by + * getTransferType() and length getNumDataElements(). + * If null an array of appropriate type and size will be + * allocated. + * @return An object reference to an array of type defined by + * getTransferType() with the request pixel data. + */ + public Object getDataElements(int x, int y, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x >= this.maxX) || (y >= this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short outData[]; + if (obj == null) { + outData = new short[numDataElements]; + } else { + outData = (short[])obj; + } + int off = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + + for (int band = 0; band < numDataElements; band++) { + outData[band] = data[dataOffsets[band] + off]; + } + + return outData; + } + + /** + * Returns an array of data elements from the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + *

+     *       short[] bandData = (short[])Raster.getDataElements(x, y, w, h, null);
+     *       int numDataElements = Raster.getBands();
+     *       short[] pixel = new short[numDataElements];
+     *       // To find the data element at location (x2, y2)
+     *       System.arraycopy(bandData, ((y2-y)*w + (x2-x))*numDataElements,
+     *                        pixel, 0, numDataElements);
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param outData An object reference to an array of type defined by + * getTransferType() and length w*h*getNumDataElements(). + * If null an array of appropriate type and size will be + * allocated. + * @return An object reference to an array of type defined by + * getTransferType() with the request pixel data. + */ + public Object getDataElements(int x, int y, int w, int h, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short outData[]; + if (obj == null) { + outData = new short[w*h*numDataElements]; + } else { + outData = (short[])obj; + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + + int xoff; + int off = 0; + int xstart; + int ystart; + + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + for (int c = 0; c < numDataElements; c++) { + outData[off++] = data[dataOffsets[c] + xoff]; + } + } + } + + return outData; + } + + /** + * Returns a short integer array of data elements from the + * specified rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       short[] bandData = Raster.getShortData(x, y, w, h, null);
+     *       // To find the data element at location (x2, y2)
+     *       short dataElenent = bandData[((y2-y)*w + (x2-x))];
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the sample rectangle. + * @param height Height of the sample rectangle. + * @param band The band to return. + * @param outData If non-null, data elements for all bands + * at the specified location are returned in this array. + * @return Data array with data elements for all bands. + */ + public short[] getShortData(int x, int y, int w, int h, + int band, short[] outData) { + // Bounds check for 'band' will be performed automatically + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + if (outData == null) { + outData = new short[numDataElements*w*h]; + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride+ dataOffsets[band]; + int xoff; + int off = 0; + int xstart; + int ystart; + + if (pixelStride == 1) { + if (scanlineStride == w) { + System.arraycopy(data, yoff, outData, 0, w*h); + } + else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + System.arraycopy(data, yoff, outData, off, w); + off += w; + } + } + } + else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + outData[off++] = data[xoff]; + } + } + } + + return outData; + } + + /** + * Returns a short integer array of data elements from the + * specified rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       short[] bandData = Raster.getShortData(x, y, w, h, null);
+     *       int numDataElements = Raster.getNumBands();
+     *       short[] pixel = new short[numDataElements];
+     *       // To find the data element at location (x2, y2)
+     *       System.arraycopy(bandData, ((y2-y)*w + (x2-x))*numDataElements,
+     *                        pixel, 0, numDataElements);
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param outData If non-null, data elements for all bands + * at the specified location are returned in this array. + * @return Data array with data elements for all bands. + */ + public short[] getShortData(int x, int y, int w, int h, short[] outData) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + if (outData == null) { + outData = new short[numDataElements*w*h]; + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + int xoff; + int off = 0; + int xstart; + int ystart; + + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + for (int c = 0; c < numDataElements; c++) { + outData[off++] = data[dataOffsets[c] + xoff]; + } + } + } + + return outData; + } + + /** + * Stores the data elements for all bands at the specified location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param inData An object reference to an array of type defined by + * getTransferType() and length getNumDataElements() + * containing the pixel data to place at x,y. + */ + public void setDataElements(int x, int y, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x >= this.maxX) || (y >= this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short inData[] = (short[])obj; + int off = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + for (int i = 0; i < numDataElements; i++) { + data[dataOffsets[i] + off] = (short) inData[i]; + } + + markDirty(); + } + + /** + * Stores the Raster data at the specified location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param inRaster Raster of data to place at x,y location. + */ + public void setDataElements(int x, int y, Raster inRaster) { + int dstOffX = x + inRaster.getMinX(); + int dstOffY = y + inRaster.getMinY(); + int width = inRaster.getWidth(); + int height = inRaster.getHeight(); + if ((dstOffX < this.minX) || (dstOffY < this.minY) || + (dstOffX + width > this.maxX) || (dstOffY + height > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + + setDataElements(dstOffX, dstOffY, width, height, inRaster); + } + + /** + * Stores the Raster data at the specified location. + * @param dstX The absolute X coordinate of the destination pixel + * that will receive a copy of the upper-left pixel of the + * inRaster + * @param dstY The absolute Y coordinate of the destination pixel + * that will receive a copy of the upper-left pixel of the + * inRaster + * @param width The number of pixels to store horizontally + * @param height The number of pixels to store vertically + * @param inRaster Raster of data to place at x,y location. + */ + private void setDataElements(int dstX, int dstY, + int width, int height, + Raster inRaster) { + // Assume bounds checking has been performed previously + if (width <= 0 || height <= 0) { + return; + } + + // Write inRaster (minX, minY) to (dstX, dstY) + + int srcOffX = inRaster.getMinX(); + int srcOffY = inRaster.getMinY(); + Object tdata = null; + +// // REMIND: Do something faster! +// if (inRaster instanceof ShortComponentRaster) { +// } + + for (int startY=0; startY < height; startY++) { + // Grab one scanline at a time + tdata = inRaster.getDataElements(srcOffX, srcOffY+startY, + width, 1, tdata); + setDataElements(dstX, dstY + startY, width, 1, tdata); + } + } + + /** + * Stores an array of data elements into the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param inData An object reference to an array of type defined by + * getTransferType() and length w*h*getNumDataElements() + * containing the pixel data to place between x,y and + * x+h, y+h. + */ + public void setDataElements(int x, int y, int w, int h, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short inData[] = (short[])obj; + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + int xoff; + int off = 0; + int xstart; + int ystart; + + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + for (int c = 0; c < numDataElements; c++) { + data[dataOffsets[c] + xoff] = (short) inData[off++]; + } + } + } + + markDirty(); + } + + /** + * Stores a short integer array of data elements into the + * specified rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param band The band to set. + * @param inData The data elements to be stored. + */ + public void putShortData(int x, int y, int w, int h, + int band, short[] inData) { + // Bounds check for 'band' will be performed automatically + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride + dataOffsets[band]; + int xoff; + int off = 0; + int xstart; + int ystart; + + if (pixelStride == 1) { + if (scanlineStride == w) { + System.arraycopy(inData, 0, data, yoff, w*h); + } + else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + System.arraycopy(inData, off, data, yoff, w); + off += w; + } + } + } + else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + data[xoff] = inData[off++]; + } + } + } + + markDirty(); + } + + /** + * Stores a short integer array of data elements into the + * specified rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param inData The data elements to be stored. + */ + public void putShortData(int x, int y, int w, int h, short[] inData) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + int xoff; + int off = 0; + int xstart; + int ystart; + + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + for (int c = 0; c < numDataElements; c++) { + data[dataOffsets[c] + xoff] = inData[off++]; + } + } + } + + markDirty(); + } + + /** + * Creates a subraster given a region of the raster. The x and y + * coordinates specify the horizontal and vertical offsets + * from the upper-left corner of this raster to the upper-left corner + * of the subraster. A subset of the bands of the parent Raster may + * be specified. If this is null, then all the bands are present in the + * subRaster. A translation to the subRaster may also be specified. + * Note that the subraster will reference the same + * band objects as the parent raster, but using different offsets. + * @param x X offset. + * @param y Y offset. + * @param width Width (in pixels) of the subraster. + * @param height Height (in pixels) of the subraster. + * @param x0 Translated X origin of the subraster. + * @param y0 Translated Y origin of the subraster. + * @param bandList Array of band indices. + * @exception RasterFormatException + * if the specified bounding box is outside of the parent raster. + */ + public Raster createChild (int x, int y, + int width, int height, + int x0, int y0, int[] bandList) { + WritableRaster newRaster = createWritableChild(x, y, + width, height, + x0, y0, + bandList); + return (Raster) newRaster; + } + + /** + * Creates a Writable subRaster given a region of the Raster. The x and y + * coordinates specify the horizontal and vertical offsets + * from the upper-left corner of this Raster to the upper-left corner + * of the subRaster. A subset of the bands of the parent Raster may + * be specified. If this is null, then all the bands are present in the + * subRaster. A translation to the subRaster may also be specified. + * Note that the subRaster will reference the same + * DataBuffers as the parent Raster, but using different offsets. + * @param x X offset. + * @param y Y offset. + * @param width Width (in pixels) of the subraster. + * @param height Height (in pixels) of the subraster. + * @param x0 Translated X origin of the subraster. + * @param y0 Translated Y origin of the subraster. + * @param bandList Array of band indices. + * @exception RasterFormatException + * if the specified bounding box is outside of the parent Raster. + */ + public WritableRaster createWritableChild(int x, int y, + int width, int height, + int x0, int y0, + int[] bandList) { + if (x < this.minX) { + throw new RasterFormatException("x lies outside the raster"); + } + if (y < this.minY) { + throw new RasterFormatException("y lies outside the raster"); + } + if ((x+width < x) || (x+width > this.minX + this.width)) { + throw new RasterFormatException("(x + width) is outside of Raster"); + } + if ((y+height < y) || (y+height > this.minY + this.height)) { + throw new RasterFormatException("(y + height) is outside of Raster"); + } + + SampleModel sm; + + if (bandList != null) + sm = sampleModel.createSubsetSampleModel(bandList); + else + sm = sampleModel; + + int deltaX = x0 - x; + int deltaY = y0 - y; + + return new ShortComponentRaster(sm, + dataBuffer, + new Rectangle(x0, y0, width, height), + new Point(sampleModelTranslateX+deltaX, + sampleModelTranslateY+deltaY), + this); + } + + /** + * Creates a Raster with the same layout but using a different + * width and height, and with new zeroed data arrays. + */ + public WritableRaster createCompatibleWritableRaster(int w, int h) { + if (w <= 0 || h <=0) { + throw new RasterFormatException("negative "+ + ((w <= 0) ? "width" : "height")); + } + + SampleModel sm = sampleModel.createCompatibleSampleModel(w, h); + + return new ShortComponentRaster(sm, new Point(0, 0)); + } + + /** + * Creates a Raster with the same layout and the same + * width and height, and with new zeroed data arrays. If + * the Raster is a subRaster, this will call + * createCompatibleRaster(width, height). + */ + public WritableRaster createCompatibleWritableRaster() { + return createCompatibleWritableRaster(width,height); + } + + /** + * Verify that the layout parameters are consistent with the data. + * + * The method verifies whether scanline stride and pixel stride do not + * cause an integer overflow during calculation of a position of the pixel + * in data buffer. It also verifies whether the data buffer has enough data + * to correspond the raster layout attributes. + * + * @throws RasterFormatException if an integer overflow is detected, + * or if data buffer has not enough capacity. + */ + protected final void verify() { + /* Need to re-verify the dimensions since a sample model may be + * specified to the constructor + */ + if (width <= 0 || height <= 0 || + height > (Integer.MAX_VALUE / width)) + { + throw new RasterFormatException("Invalid raster dimension"); + } + + for (int i = 0; i < dataOffsets.length; i++) { + if (dataOffsets[i] < 0) { + throw new RasterFormatException("Data offsets for band " + i + + "(" + dataOffsets[i] + + ") must be >= 0"); + } + } + + if ((long)minX - sampleModelTranslateX < 0 || + (long)minY - sampleModelTranslateY < 0) { + + throw new RasterFormatException("Incorrect origin/translate: (" + + minX + ", " + minY + ") / (" + + sampleModelTranslateX + ", " + sampleModelTranslateY + ")"); + } + + // we can be sure that width and height are greater than 0 + if (scanlineStride < 0 || + scanlineStride > (Integer.MAX_VALUE / height)) + { + // integer overflow + throw new RasterFormatException("Incorrect scanline stride: " + + scanlineStride); + } + + if (height > 1 || minY - sampleModelTranslateY > 0) { + // buffer should contain at least one scanline + if (scanlineStride > data.length) { + throw new RasterFormatException("Incorrect scanline stride: " + + scanlineStride); + } + } + + int lastScanOffset = (height - 1) * scanlineStride; + + if (pixelStride < 0 || + pixelStride > (Integer.MAX_VALUE / width) || + pixelStride > data.length) + { + // integer overflow + throw new RasterFormatException("Incorrect pixel stride: " + + pixelStride); + } + int lastPixelOffset = (width - 1) * pixelStride; + + if (lastPixelOffset > (Integer.MAX_VALUE - lastScanOffset)) { + // integer overflow + throw new RasterFormatException("Incorrect raster attributes"); + } + lastPixelOffset += lastScanOffset; + + int index; + int maxIndex = 0; + for (int i = 0; i < numDataElements; i++) { + if (dataOffsets[i] > (Integer.MAX_VALUE - lastPixelOffset)) { + throw new RasterFormatException("Incorrect band offset: " + + dataOffsets[i]); + } + + index = lastPixelOffset + dataOffsets[i]; + + if (index > maxIndex) { + maxIndex = index; + } + } + if (data.length <= maxIndex) { + throw new RasterFormatException("Data array too small (should be > " + + maxIndex + " )"); + } + } + + public String toString() { + return new String ("ShortComponentRaster: width = "+width + +" height = " + height + +" #numDataElements "+numDataElements); + // +" xOff = "+xOffset+" yOff = "+yOffset); + } + +} diff --git a/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortInterleavedRaster.java b/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortInterleavedRaster.java new file mode 100644 index 000000000..80bec3927 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/awt/image/ShortInterleavedRaster.java @@ -0,0 +1,772 @@ +/* + * Copyright (c) 1998, 2012, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.awt.image; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.RasterFormatException; +import java.awt.image.SampleModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferUShort; +import java.awt.Rectangle; +import java.awt.Point; + +/** + * This class defines a Raster with pixels consisting of one or more 16-bit + * data elements stored in close proximity to each other in a short integer + * array. The bit precision per data element is that + * of the data type (that is, the bit precision for this Raster is 16). + * There is only one pixel stride and one scanline stride for all + * bands. This type of Raster can be used with a + * ComponentColorModel if there are multiple bands, or a + * IndexColorModel if there is only one band. + *

+ * For example, 5-6-5 RGB image data can be represented by a + * ShortInterleavedRaster using a SinglePixelPackedSampleModel and + * a ComponentColorModel. + * + * + */ +public class ShortInterleavedRaster extends ShortComponentRaster { + + /** A cached copy of minX + width for use in bounds checks. */ + private int maxX; + + /** A cached copy of minY + height for use in bounds checks. */ + private int maxY; + + /** + * Constructs a ShortInterleavedRaster with the given SampleModel. + * The Raster's upper left corner is origin and it is the same + * size as the SampleModel. A DataBuffer large enough to describe the + * Raster is automatically created. SampleModel must be of type + * PixelInterleavedSampleModel or SinglePixelPackedSampleModel. + * @param sampleModel The SampleModel that specifies the layout. + * @param origin The Point that specified the origin. + */ + public ShortInterleavedRaster(SampleModel sampleModel, Point origin) { + this(sampleModel, + sampleModel.createDataBuffer(), + new Rectangle(origin.x, + origin.y, + sampleModel.getWidth(), + sampleModel.getHeight()), + origin, + null); + } + + /** + * Constructs a ShortInterleavedRaster with the given SampleModel + * and DataBuffer. The Raster's upper left corner is origin and + * it is the same sizes the SampleModel. The DataBuffer is not + * initialized and must be a DataBufferUShort compatible with SampleModel. + * SampleModel must be of type PixelInterleavedSampleModel or + * SinglePixelPackedSampleModel. + * @param sampleModel The SampleModel that specifies the layout. + * @param dataBuffer The DataBufferUShort that contains the image data. + * @param origin The Point that specifies the origin. + */ + public ShortInterleavedRaster(SampleModel sampleModel, + DataBuffer dataBuffer, + Point origin) { + this(sampleModel, + dataBuffer, + new Rectangle(origin.x, + origin.y, + sampleModel.getWidth(), + sampleModel.getHeight()), + origin, + null); + } + + /** + * Constructs a ShortInterleavedRaster with the given SampleModel, + * DataBuffer, and parent. DataBuffer must be a DataBufferUShort and + * SampleModel must be of type PixelInterleavedSampleModel or + * SinglePixelPackedSampleModel. When translated into the base Raster's + * coordinate system, aRegion must be contained by the base Raster. + * Origin is the coodinate in the new Raster's coordinate system of + * the origin of the base Raster. (The base Raster is the Raster's + * ancestor which has no parent.) + * + * Note that this constructor should generally be called by other + * constructors or create methods, it should not be used directly. + * @param sampleModel The SampleModel that specifies the layout. + * @param dataBuffer The DataBufferUShort that contains the image data. + * @param aRegion The Rectangle that specifies the image area. + * @param origin The Point that specifies the origin. + * @param parent The parent (if any) of this raster. + */ + public ShortInterleavedRaster(SampleModel sampleModel, + DataBuffer dataBuffer, + Rectangle aRegion, + Point origin, + ShortInterleavedRaster parent) { + + super(sampleModel, dataBuffer, aRegion, origin, parent); + this.maxX = minX + width; + this.maxY = minY + height; + + if(!(dataBuffer instanceof DataBufferUShort)) { + throw new RasterFormatException("ShortInterleavedRasters must "+ + "have ushort DataBuffers"); + } + + DataBufferUShort dbus = (DataBufferUShort)dataBuffer; + this.data = stealData(dbus, 0); + + // REMIND: need case for interleaved ComponentSampleModel + if ((sampleModel instanceof PixelInterleavedSampleModel) || + (sampleModel instanceof ComponentSampleModel && + sampleModel.getNumBands() == 1)) { + ComponentSampleModel csm = (ComponentSampleModel)sampleModel; + + this.scanlineStride = csm.getScanlineStride(); + this.pixelStride = csm.getPixelStride(); + this.dataOffsets = csm.getBandOffsets(); + int xOffset = aRegion.x - origin.x; + int yOffset = aRegion.y - origin.y; + for (int i = 0; i < getNumDataElements(); i++) { + dataOffsets[i] += xOffset*pixelStride+yOffset*scanlineStride; + } + } else if (sampleModel instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = + (SinglePixelPackedSampleModel)sampleModel; + this.scanlineStride = sppsm.getScanlineStride(); + this.pixelStride = 1; + this.dataOffsets = new int[1]; + this.dataOffsets[0] = dbus.getOffset(); + int xOffset = aRegion.x - origin.x; + int yOffset = aRegion.y - origin.y; + dataOffsets[0] += xOffset+yOffset*scanlineStride; + } else { + throw new RasterFormatException("ShortInterleavedRasters must "+ + "have PixelInterleavedSampleModel, SinglePixelPackedSampleModel"+ + " or 1 band ComponentSampleModel. Sample model is "+ + sampleModel); + } + this.bandOffset = this.dataOffsets[0]; + verify(); + } + + /** + * Returns a copy of the data offsets array. For each band the data offset + * is the index into the band's data array, of the first sample of the + * band. + */ + public int[] getDataOffsets() { + return (int[]) dataOffsets.clone(); + } + + /** + * Returns the data offset for the specified band. The data offset + * is the index into the data array in which the first sample + * of the first scanline is stored. + * @param band The band whose offset is returned. + */ + public int getDataOffset(int band) { + return dataOffsets[band]; + } + + /** + * Returns the scanline stride -- the number of data array elements between + * a given sample and the same sample in the same column of the next row. + */ + public int getScanlineStride() { + return scanlineStride; + } + + /** + * Returns pixel stride -- the number of data array elements between two + * samples for the same band on the same scanline. + */ + public int getPixelStride() { + return pixelStride; + } + + /** + * Returns a reference to the data array. + */ + public short[] getDataStorage() { + return data; + } + + /** + * Returns the data elements for all bands at the specified + * location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param outData An object reference to an array of type defined by + * getTransferType() and length getNumDataElements(). + * If null an array of appropriate type and size will be + * allocated. + * @return An object reference to an array of type defined by + * getTransferType() with the request pixel data. + */ + public Object getDataElements(int x, int y, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x >= this.maxX) || (y >= this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short outData[]; + if (obj == null) { + outData = new short[numDataElements]; + } else { + outData = (short[])obj; + } + int off = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + + for (int band = 0; band < numDataElements; band++) { + outData[band] = data[dataOffsets[band] + off]; + } + + return outData; + } + + /** + * Returns an array of data elements from the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + *

+     *       short[] bandData = (short[])Raster.getDataElements(x, y, w, h, null);
+     *       int numDataElements = Raster.getBands();
+     *       short[] pixel = new short[numDataElements];
+     *       // To find the data element at location (x2, y2)
+     *       System.arraycopy(bandData, ((y2-y)*w + (x2-x))*numDataElements,
+     *                        pixel, 0, numDataElements);
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param outData An object reference to an array of type defined by + * getTransferType() and length w*h*getNumDataElements(). + * If null an array of appropriate type and size will be + * allocated. + * @return An object reference to an array of type defined by + * getTransferType() with the request pixel data. + */ + public Object getDataElements(int x, int y, int w, int h, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short outData[]; + if (obj == null) { + outData = new short[w*h*numDataElements]; + } else { + outData = (short[])obj; + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + + int xoff; + int off = 0; + int xstart; + int ystart; + + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + for (int c = 0; c < numDataElements; c++) { + outData[off++] = data[dataOffsets[c] + xoff]; + } + } + } + + return outData; + } + + /** + * Returns a short integer array of data elements from the + * specified rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       short[] bandData = Raster.getShortData(x, y, w, h, null);
+     *       // To find the data element at location (x2, y2)
+     *       short dataElenent = bandData[((y2-y)*w + (x2-x))];
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the sample rectangle. + * @param height Height of the sample rectangle. + * @param band The band to return. + * @param outData If non-null, data elements for all bands + * at the specified location are returned in this array. + * @return Data array with data elements for all bands. + */ + public short[] getShortData(int x, int y, int w, int h, + int band, short[] outData) { + // Bounds check for 'band' will be performed automatically + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + if (outData == null) { + outData = new short[numDataElements*w*h]; + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride+ dataOffsets[band]; + int xoff; + int off = 0; + int xstart; + int ystart; + + if (pixelStride == 1) { + if (scanlineStride == w) { + System.arraycopy(data, yoff, outData, 0, w*h); + } + else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + System.arraycopy(data, yoff, outData, off, w); + off += w; + } + } + } + else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + outData[off++] = data[xoff]; + } + } + } + + return outData; + } + + /** + * Returns a short integer array of data elements from the + * specified rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + *
+     *       short[] bandData = Raster.getShortData(x, y, w, h, null);
+     *       int numDataElements = Raster.getNumBands();
+     *       short[] pixel = new short[numDataElements];
+     *       // To find the data element at location (x2, y2)
+     *       System.arraycopy(bandData, ((y2-y)*w + (x2-x))*numDataElements,
+     *                        pixel, 0, numDataElements);
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param width Width of the pixel rectangle. + * @param height Height of the pixel rectangle. + * @param outData If non-null, data elements for all bands + * at the specified location are returned in this array. + * @return Data array with data elements for all bands. + */ + public short[] getShortData(int x, int y, int w, int h, short[] outData) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + if (outData == null) { + outData = new short[numDataElements*w*h]; + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + int xoff; + int off = 0; + int xstart; + int ystart; + + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + for (int c = 0; c < numDataElements; c++) { + outData[off++] = data[dataOffsets[c] + xoff]; + } + } + } + + return outData; + } + + /** + * Stores the data elements for all bands at the specified location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinate is out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param inData An object reference to an array of type defined by + * getTransferType() and length getNumDataElements() + * containing the pixel data to place at x,y. + */ + public void setDataElements(int x, int y, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x >= this.maxX) || (y >= this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short inData[] = (short[])obj; + int off = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + for (int i = 0; i < numDataElements; i++) { + data[dataOffsets[i] + off] = (short) inData[i]; + } + markDirty(); + } + + /** + * Stores the Raster data at the specified location. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * @param x The X coordinate of the pixel location. + * @param y The Y coordinate of the pixel location. + * @param inRaster Raster of data to place at x,y location. + */ + public void setDataElements(int x, int y, Raster inRaster) { + int dstOffX = x + inRaster.getMinX(); + int dstOffY = y + inRaster.getMinY(); + int width = inRaster.getWidth(); + int height = inRaster.getHeight(); + if ((dstOffX < this.minX) || (dstOffY < this.minY) || + (dstOffX + width > this.maxX) || (dstOffY + height > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + + setDataElements(dstOffX, dstOffY, width, height, inRaster); + } + + /** + * Stores the Raster data at the specified location. + * @param dstX The absolute X coordinate of the destination pixel + * that will receive a copy of the upper-left pixel of the + * inRaster + * @param dstY The absolute Y coordinate of the destination pixel + * that will receive a copy of the upper-left pixel of the + * inRaster + * @param width The number of pixels to store horizontally + * @param height The number of pixels to store vertically + * @param inRaster Raster of data to place at x,y location. + */ + private void setDataElements(int dstX, int dstY, + int width, int height, + Raster inRaster) { + // Assume bounds checking has been performed previously + if (width <= 0 || height <= 0) { + return; + } + + // Write inRaster (minX, minY) to (dstX, dstY) + + int srcOffX = inRaster.getMinX(); + int srcOffY = inRaster.getMinY(); + Object tdata = null; + +// REMIND: Do something faster! +// if (inRaster instanceof ShortInterleavedRaster) { +// } + + for (int startY=0; startY < height; startY++) { + // Grab one scanline at a time + tdata = inRaster.getDataElements(srcOffX, srcOffY+startY, + width, 1, tdata); + setDataElements(dstX, dstY + startY, width, 1, tdata); + } + } + + /** + * Stores an array of data elements into the specified rectangular + * region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * A ClassCastException will be thrown if the input object is non null + * and references anything other than an array of transferType. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param inData An object reference to an array of type defined by + * getTransferType() and length w*h*getNumDataElements() + * containing the pixel data to place between x,y and + * x+h, y+h. + */ + public void setDataElements(int x, int y, int w, int h, Object obj) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + short inData[] = (short[])obj; + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + int xoff; + int off = 0; + int xstart; + int ystart; + + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + for (int c = 0; c < numDataElements; c++) { + data[dataOffsets[c] + xoff] = (short) inData[off++]; + } + } + } + + markDirty(); + } + + /** + * Stores a short integer array of data elements into the + * specified rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param band The band to set. + * @param inData The data elements to be stored. + */ + public void putShortData(int x, int y, int w, int h, + int band, short[] inData) { + // Bounds check for 'band' will be performed automatically + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride + dataOffsets[band]; + int xoff; + int off = 0; + int xstart; + int ystart; + + if (pixelStride == 1) { + if (scanlineStride == w) { + System.arraycopy(inData, 0, data, yoff, w*h); + } + else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + System.arraycopy(inData, off, data, yoff, w); + off += w; + } + } + } + else { + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + data[xoff] = inData[off++]; + } + } + } + + markDirty(); + } + + /** + * Stores a short integer array of data elements into the + * specified rectangular region. + * An ArrayIndexOutOfBounds exception will be thrown at runtime + * if the pixel coordinates are out of bounds. + * The data elements in the + * data array are assumed to be packed. That is, a data element + * for the nth band at location (x2, y2) would be found at: + *
+     *      inData[((y2-y)*w + (x2-x))*numDataElements + n]
+     * 
+ * @param x The X coordinate of the upper left pixel location. + * @param y The Y coordinate of the upper left pixel location. + * @param w Width of the pixel rectangle. + * @param h Height of the pixel rectangle. + * @param inData The data elements to be stored. + */ + public void putShortData(int x, int y, int w, int h, short[] inData) { + if ((x < this.minX) || (y < this.minY) || + (x + w > this.maxX) || (y + h > this.maxY)) { + throw new ArrayIndexOutOfBoundsException + ("Coordinate out of bounds!"); + } + int yoff = (y-minY)*scanlineStride + + (x-minX)*pixelStride; + int xoff; + int off = 0; + int xstart; + int ystart; + + for (ystart=0; ystart < h; ystart++, yoff += scanlineStride) { + xoff = yoff; + for (xstart=0; xstart < w; xstart++, xoff += pixelStride) { + for (int c = 0; c < numDataElements; c++) { + data[dataOffsets[c] + xoff] = inData[off++]; + } + } + } + + markDirty(); + } + + /** + * Creates a subraster given a region of the raster. The x and y + * coordinates specify the horizontal and vertical offsets + * from the upper-left corner of this raster to the upper-left corner + * of the subraster. A subset of the bands of the parent Raster may + * be specified. If this is null, then all the bands are present in the + * subRaster. A translation to the subRaster may also be specified. + * Note that the subraster will reference the same + * band objects as the parent raster, but using different offsets. + * @param x X offset. + * @param y Y offset. + * @param width Width (in pixels) of the subraster. + * @param height Height (in pixels) of the subraster. + * @param x0 Translated X origin of the subraster. + * @param y0 Translated Y origin of the subraster. + * @param bandList Array of band indices. + * @exception RasterFormatException + * if the specified bounding box is outside of the parent raster. + */ + public Raster createChild (int x, int y, + int width, int height, + int x0, int y0, int[] bandList) { + WritableRaster newRaster = createWritableChild(x, y, + width, height, + x0, y0, + bandList); + return (Raster) newRaster; + } + + /** + * Creates a Writable subRaster given a region of the Raster. The x and y + * coordinates specify the horizontal and vertical offsets + * from the upper-left corner of this Raster to the upper-left corner + * of the subRaster. A subset of the bands of the parent Raster may + * be specified. If this is null, then all the bands are present in the + * subRaster. A translation to the subRaster may also be specified. + * Note that the subRaster will reference the same + * DataBuffers as the parent Raster, but using different offsets. + * @param x X offset. + * @param y Y offset. + * @param width Width (in pixels) of the subraster. + * @param height Height (in pixels) of the subraster. + * @param x0 Translated X origin of the subraster. + * @param y0 Translated Y origin of the subraster. + * @param bandList Array of band indices. + * @exception RasterFormatException + * if the specified bounding box is outside of the parent Raster. + */ + public WritableRaster createWritableChild(int x, int y, + int width, int height, + int x0, int y0, + int[] bandList) { + if (x < this.minX) { + throw new RasterFormatException("x lies outside the raster"); + } + if (y < this.minY) { + throw new RasterFormatException("y lies outside the raster"); + } + if ((x+width < x) || (x+width > this.minX + this.width)) { + throw new RasterFormatException("(x + width) is outside of Raster"); + } + if ((y+height < y) || (y+height > this.minY + this.height)) { + throw new RasterFormatException("(y + height) is outside of Raster"); + } + + SampleModel sm; + + if (bandList != null) + sm = sampleModel.createSubsetSampleModel(bandList); + else + sm = sampleModel; + + int deltaX = x0 - x; + int deltaY = y0 - y; + + return new ShortInterleavedRaster(sm, + dataBuffer, + new Rectangle(x0, y0, width, height), + new Point(sampleModelTranslateX+deltaX, + sampleModelTranslateY+deltaY), + this); + } + + /** + * Creates a Raster with the same layout but using a different + * width and height, and with new zeroed data arrays. + */ + public WritableRaster createCompatibleWritableRaster(int w, int h) { + if (w <= 0 || h <=0) { + throw new RasterFormatException("negative "+ + ((w <= 0) ? "width" : "height")); + } + + SampleModel sm = sampleModel.createCompatibleSampleModel(w, h); + + return new ShortInterleavedRaster(sm, new Point(0, 0)); + } + + /** + * Creates a Raster with the same layout and the same + * width and height, and with new zeroed data arrays. If + * the Raster is a subRaster, this will call + * createCompatibleRaster(width, height). + */ + public WritableRaster createCompatibleWritableRaster() { + return createCompatibleWritableRaster(width,height); + } + + public String toString() { + return new String ("ShortInterleavedRaster: width = "+width + +" height = " + height + +" #numDataElements "+numDataElements); + // +" xOff = "+xOffset+" yOff = "+yOffset); + } + +} diff --git a/sources/net.sf.j2s.java.core/src/sun/awt/image/SunWritableRaster.java b/sources/net.sf.j2s.java.core/src/sun/awt/image/SunWritableRaster.java index 8378eff3f..86a043ae9 100644 --- a/sources/net.sf.j2s.java.core/src/sun/awt/image/SunWritableRaster.java +++ b/sources/net.sf.j2s.java.core/src/sun/awt/image/SunWritableRaster.java @@ -30,6 +30,7 @@ import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferUShort; import java.awt.image.Raster; import java.awt.image.SampleModel; import java.awt.image.WritableRaster; @@ -58,11 +59,12 @@ public byte[] getData(DataBufferByte dbb, int bank) { return dbb.bankdata[bank]; } - // SwingJS not implemented public short[] getData(DataBufferUShort dbus, - // int bank) { - // return dbus.bankdata[bank]; - // } - // + @Override + public short[] getData(DataBufferUShort dbus, + int bank) { + return dbus.bankdata[bank]; + } + @Override public int[] getData(DataBufferInt dbi, int bank) { return dbi.bankdata[bank]; @@ -95,10 +97,10 @@ public static byte[] stealData(DataBufferByte dbb, int bank) { return getStealer().getData(dbb, bank); } - // public static short[] stealData(DataBufferUShort dbus, int bank) { - // return getStealer().getData(dbus, bank); - // } - // + public static short[] stealData(DataBufferUShort dbus, int bank) { + return getStealer().getData(dbus, bank); + } + public static int[] stealData(DataBufferInt dbi, int bank) { return getStealer().getData(dbi, bank); } diff --git a/sources/net.sf.j2s.java.core/src/sun/nio/cs/StreamDecoder.java b/sources/net.sf.j2s.java.core/src/sun/nio/cs/StreamDecoder.java index 5f1ec4ed8..acbee0e1f 100644 --- a/sources/net.sf.j2s.java.core/src/sun/nio/cs/StreamDecoder.java +++ b/sources/net.sf.j2s.java.core/src/sun/nio/cs/StreamDecoder.java @@ -242,7 +242,7 @@ private static FileChannel getChannel(FileInputStream in) { this.cs = dec.charset(); this.decoder = dec; - // This path disabled until direct buffers are faster + // Java8 comment: This path disabled until direct buffers are faster if (false && in instanceof FileInputStream) { ch = getChannel((FileInputStream)in); if (ch != null) diff --git a/sources/net.sf.j2s.java.core/src/sun/nio/cs/StreamEncoder.java b/sources/net.sf.j2s.java.core/src/sun/nio/cs/StreamEncoder.java index a3aaf3675..03f83e912 100644 --- a/sources/net.sf.j2s.java.core/src/sun/nio/cs/StreamEncoder.java +++ b/sources/net.sf.j2s.java.core/src/sun/nio/cs/StreamEncoder.java @@ -185,7 +185,7 @@ private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) { this.cs = enc.charset(); this.encoder = enc; - // This path disabled until direct buffers are faster + // Java8 comment: This path disabled until direct buffers are faster if (false && out instanceof FileOutputStream) { ch = ((FileOutputStream)out).getChannel(); if (ch != null) diff --git a/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationInvocationHandler.java b/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationInvocationHandler.java new file mode 100644 index 000000000..a78b9b03c --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationInvocationHandler.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2003, 2014, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.reflect.annotation; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.annotation.AnnotationFormatError; +import java.lang.annotation.IncompleteAnnotationException; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.Map; + +/** + * InvocationHandler for dynamic proxy implementation of Annotation. + * + * @author Josh Bloch + * @since 1.5 + */ +class AnnotationInvocationHandler implements InvocationHandler { + private final Class type; + private final Map memberValues; + + AnnotationInvocationHandler(Class type, Map memberValues) { + this.type = type; + this.memberValues = memberValues; + } + + public Object invoke(Object proxy, Method method, Object[] args) { + String member = method.getName(); + Class[] paramTypes = method.getParameterTypes(); + + // Handle Object and Annotation methods + if (member.equals("equals") && paramTypes.length == 1 && + paramTypes[0] == Object.class) + return equalsImpl(args[0]); + if (paramTypes.length != 0) + throw new AssertionError("Too many parameters for an annotation method"); + + switch(member) { + case "toString": + return toStringImpl(); + case "hashCode": + return hashCodeImpl(); + case "annotationType": + return type; + } + + // Handle annotation member accessors + Object result = memberValues.get(member); + +// if (result == null) +// throw new IncompleteAnnotationException(type, member); +// +// if (result instanceof ExceptionProxy) +// throw ((ExceptionProxy) result).generateException(); +// + if (result.getClass().isArray() && Array.getLength(result) != 0) + result = cloneArray(result); + + return result; + } + + private String toStringImpl() { + return "[" + type + "]"; + } + + /** + * This method, which clones its array argument, would not be necessary + * if Cloneable had a public clone method. + */ + private Object cloneArray(Object array) { + Class type = array.getClass(); + + if (type == byte[].class) { + byte[] byteArray = (byte[])array; + return byteArray.clone(); + } + if (type == char[].class) { + char[] charArray = (char[])array; + return charArray.clone(); + } + if (type == double[].class) { + double[] doubleArray = (double[])array; + return doubleArray.clone(); + } + if (type == float[].class) { + float[] floatArray = (float[])array; + return floatArray.clone(); + } + if (type == int[].class) { + int[] intArray = (int[])array; + return intArray.clone(); + } + if (type == long[].class) { + long[] longArray = (long[])array; + return longArray.clone(); + } + if (type == short[].class) { + short[] shortArray = (short[])array; + return shortArray.clone(); + } + if (type == boolean[].class) { + boolean[] booleanArray = (boolean[])array; + return booleanArray.clone(); + } + + Object[] objectArray = (Object[])array; + return objectArray.clone(); + } + + + + /** + * Implementation of dynamicProxy.equals(Object o) + */ + private Boolean equalsImpl(Object o) { + if (o == this) + return true; + + if (!type.isInstance(o)) + return false; + for (Method memberMethod : getMemberMethods()) { + String member = memberMethod.getName(); + Object ourValue = memberValues.get(member); + Object hisValue = null; + AnnotationInvocationHandler hisHandler = asOneOfUs(o); + if (hisHandler != null) { + hisValue = hisHandler.memberValues.get(member); + } else { + try { + hisValue = memberMethod.invoke(o); + } catch (InvocationTargetException e) { + return false; + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + if (!memberValueEquals(ourValue, hisValue)) + return false; + } + return true; + } + + /** + * Returns an object's invocation handler if that object is a dynamic + * proxy with a handler of type AnnotationInvocationHandler. + * Returns null otherwise. + */ + private AnnotationInvocationHandler asOneOfUs(Object o) { + if (Proxy.isProxyClass(o.getClass())) { + InvocationHandler handler = Proxy.getInvocationHandler(o); + if (handler instanceof AnnotationInvocationHandler) + return (AnnotationInvocationHandler) handler; + } + return null; + } + + /** + * Returns true iff the two member values in "dynamic proxy return form" + * are equal using the appropriate equality function depending on the + * member type. The two values will be of the same type unless one of + * the containing annotations is ill-formed. If one of the containing + * annotations is ill-formed, this method will return false unless the + * two members are identical object references. + */ + private static boolean memberValueEquals(Object v1, Object v2) { + Class type = v1.getClass(); + + // Check for primitive, string, class, enum const, annotation, + // or ExceptionProxy + if (!type.isArray()) + return v1.equals(v2); + + // Check for array of string, class, enum const, annotation, + // or ExceptionProxy + if (v1 instanceof Object[] && v2 instanceof Object[]) + return Arrays.equals((Object[]) v1, (Object[]) v2); + + // Check for ill formed annotation(s) + if (v2.getClass() != type) + return false; + + // Deal with array of primitives + if (type == byte[].class) + return Arrays.equals((byte[]) v1, (byte[]) v2); + if (type == char[].class) + return Arrays.equals((char[]) v1, (char[]) v2); + if (type == double[].class) + return Arrays.equals((double[]) v1, (double[]) v2); + if (type == float[].class) + return Arrays.equals((float[]) v1, (float[]) v2); + if (type == int[].class) + return Arrays.equals((int[]) v1, (int[]) v2); + if (type == long[].class) + return Arrays.equals((long[]) v1, (long[]) v2); + if (type == short[].class) + return Arrays.equals((short[]) v1, (short[]) v2); + assert type == boolean[].class; + return Arrays.equals((boolean[]) v1, (boolean[]) v2); + } + + /** + * Returns the member methods for our annotation type. These are + * obtained lazily and cached, as they're expensive to obtain + * and we only need them if our equals method is invoked (which should + * be rare). + */ + private Method[] getMemberMethods() { + if (memberMethods == null) { + memberMethods = type.getDeclaredMethods(); + } + return memberMethods; + } + + private transient volatile Method[] memberMethods = null; + + + /** + * Implementation of dynamicProxy.hashCode() + */ + private int hashCodeImpl() { + int result = 0; + for (Map.Entry e : memberValues.entrySet()) { + result += (127 * e.getKey().hashCode()) ^ + memberValueHashCode(e.getValue()); + } + return result; + } + + /** + * Computes hashCode of a member value (in "dynamic proxy return form") + */ + private static int memberValueHashCode(Object value) { + Class type = value.getClass(); + if (!type.isArray()) // primitive, string, class, enum const, + // or annotation + return value.hashCode(); + + if (type == byte[].class) + return Arrays.hashCode((byte[]) value); + if (type == char[].class) + return Arrays.hashCode((char[]) value); + if (type == double[].class) + return Arrays.hashCode((double[]) value); + if (type == float[].class) + return Arrays.hashCode((float[]) value); + if (type == int[].class) + return Arrays.hashCode((int[]) value); + if (type == long[].class) + return Arrays.hashCode((long[]) value); + if (type == short[].class) + return Arrays.hashCode((short[]) value); + if (type == boolean[].class) + return Arrays.hashCode((boolean[]) value); + return Arrays.hashCode((Object[]) value); + } + +} diff --git a/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationParser.java b/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationParser.java new file mode 100644 index 000000000..07033bcd9 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationParser.java @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.reflect.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.AnnotationFormatError; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Parser for Java programming language annotations. Translates annotation byte + * streams emitted by compiler into annotation objects. + * + * @author Josh Bloch + * @since 1.5 + */ +public class AnnotationParser { + public static class JSAnnotationObject { + + // [ [var/methname, typeStr, [anName1, anName2,...]], [@code1, @code2] + // ] + + public final static int ANNOT_DATA = 0; + public final static int ANNOT_CODES = 1; + + public final static int DATA_NAME = 00; + public final static int DATA_RET_TYPE_STR = 01; +// public final static int DATA_RET_TYPE = 02; + public final static int DATA_SIGNATURE = 02; + public final static int DATA_QNAMES = 03; + public final static int DATA_FIXED = 04; + + // C$.$getAnn$ = function(){ return [ + // [[null,'test.Test_Annotation_Impl',['test.Test_Annotation']],['cl="test.Test_.class" + // btest="true" itype="2" type="impl" iitype={"1" "2" "3" } ']], + // [['test1','String',['test.Test_Parameter']],['']], + // [['test2','String[]',['test.Test_Parameter']],['']], + // [['test3','int','test.Test_Item',['test.Test_Parameter']],['','']], + // [['M:name','String',['test.Test_Parameter']],['']]]}; + + /** + * First record will have null field/method name if there are class-level + * annotations. + * + * @param me + * @param isMethod + * @return true if the very first element is present and null + */ + @SuppressWarnings("unused") + private static Object[][] getAnnotations(String name, Object me, boolean isMethod) { + Object[][][] __ANN__ = /** @j2sNative me.$getAnn$ && me.$getAnn$() || */ + null; + if (__ANN__ == null) + return null; + int item = (isMethod ? DATA_SIGNATURE : DATA_NAME); + // The data are just 4 long, but we add a fifth to indicate we have + // copied all the "." items to the previous items in the list + if (__ANN__[0][ANNOT_DATA][DATA_FIXED] == null) + fixAnn(__ANN__); + for (int i = 0, n = __ANN__.length; i < n; i++) { + Object[][] rec = __ANN__[i]; + if (rec[ANNOT_DATA][item] == name) + return rec; + } + return null; + } + + private static Object[][][] fixAnn(Object[][][] __ANN__) { + Object[] lastData = null; + if (__ANN__ != null) { + for (int i = 0; i < __ANN__.length; i++) { + Object[][] o = __ANN__[i]; + Object[] data = o[ANNOT_DATA]; + if (lastData != null && data[DATA_RET_TYPE_STR] == ".") { + data[DATA_RET_TYPE_STR] = lastData[DATA_RET_TYPE_STR]; +// data[DATA_RET_TYPE] = lastData[DATA_RET_TYPE]; + } + lastData = data; + } + } + __ANN__[0][ANNOT_DATA][DATA_FIXED] = "."; + return __ANN__; + } + + public static String[] getQNames(Object[][] __ANN_REC__) { + return (String[]) __ANN_REC__[ANNOT_DATA][DATA_QNAMES]; + } + + public static String[] getAtCodes(Object[][] __ANN_REC__) { + return (String[]) __ANN_REC__[ANNOT_CODES]; + } + + /** + * + * Create a new annotation associated with some field, method, or class. + * + * Overlay all the default interface methods with methods that return the + * at-interface(key=value key=value) set. + * + * For at_interfaces - defaults - from Class. + * + * @param cl + * @return + */ + public static Method[] createMethods(Class cl) { + @SuppressWarnings("unused") + Object me = /** @j2sNative cl.$clazz$ || */ + null; + String[][] members = /** @j2sNative me.$getMembers$ && me.$getMembers$() || */ + null; + @SuppressWarnings("null") + List list = new ArrayList<>(members.length); + for (int i = members.length; --i >= 0;) { + String[] member = members[i]; + String name = member[0]; + Class retType = typeForString(member[1], false); + Object val = member[2]; + + Method method = new Method(cl, name, Class.UNKNOWN_PARAMETERS, retType, Class.NO_PARAMETERS, + Modifier.PUBLIC); + if (retType.isAnnotation()) { + val = createAnnotation(retType.getName(), (String) val); + } + method.setDefaultValue(val); + /** + * @j2sNative + * + * method.isAnnotation = true; + * + * ;(function(val, me){ + * var f = me[name] = me[name + "$"] = function(){ return val }; + * f.exName = name + "$"; + * f.exClazz = me; + * method.$meth$ = f; + * })(val, me); + */ + list.add(method); + } + return list.toArray(new Method[list.size()]); + } + + /** + * Super simple parser; allows single-level arrays only (as in annotations) + * + * @param atData + * @return + */ + private static Map getJSValues(String atData) { + Map map = new HashMap<>(); + if (atData.length() == 0) + return map; + int kpt = 0; + int apt = -1; + int qpt = -1; + List arrayData = new ArrayList<>(); + String key = null; + // SwingJS will put everything in quotes + for (int i = kpt, n = atData.length() - 1; i < n; i++) { + char c = atData.charAt(i); + if (qpt >= 0 && c != '"') + continue; + switch (c) { + default: + continue; + case '"': + if (qpt >= 0) { + String quote = atData.substring(qpt, i); + if (apt >= 0) { + arrayData.add(quote); + } else { + map.put(key, quote); + } + qpt = -1; + } else { + qpt = i + 1; + } + kpt = i + 1; + break; + case '{': + apt = i + 1; + break; + case '}': + map.put(key, arrayData); + apt = -1; + kpt = i + 1; + break; + case '\\': + i++; + break; + case '=': + if (qpt >= 0) + continue; + key = atData.substring(kpt, i).trim(); + break; + } + } + return map; + } + + public static Annotation parseAnnotation(Class annotationClass, String value) { + // TODO Auto-generated method stub + return null; + } + + + /** + * Determine the correct format from the String or String[] and then simply + * inject the key method into JavaScript. No proxy needed. + * + * @param a + * @param key + * @param o String or String[] + * @param type + * @return + * @throws ClassNotFoundException + * @throws SecurityException + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalArgumentException + * @throws IllegalAccessException + * @throws InstantiationException + */ + private static Object setLocalAnnotationValue(Annotation a, String key, Object o, Class type) + throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException, SecurityException { + Object val = o; + if (type == String.class) { + // good to go + } else if (type.isArray()) { + if (type != String[].class) { + // Only in JavaScript! + List ao = (List) o; + Class avType = type.getComponentType(); + // create the array -- might be primitive, actually. + Object[] av = (Object[]) Array.newInstance(avType, ao.size()); + for (int i = av.length; --i >= 0;) + av[i] = setLocalAnnotationValue(null, null, (String) ao.get(i), avType); + val = av; + } + } else if (type.isAnnotation()) { + + val = Class.forName(((String)val).substring(1)); + } else if (type.isEnum()) { + val = Enum.valueOf((Class) type, (String) val); + } else if (type.isPrimitive()) { + val = boxValue(val, type); + /** @j2sNative val = val.valueOf(); */ + } else if (type == Class.class) { + val = Class.forName(((String) val).replace(".class", "")); + } else { + // something else? Probably not allowed + // What does that mean: only primitive type, String, Class, *annotation* , + // enumeration are permitted or 1-dimensional arrays thereof + // doesn't seem to mean Annotation. + val = type.getConstructor(String.class).newInstance(new Object[] { val }); + } + + /** + * @j2sNative + * + * (function(val){ + * var f; + * + * a && (a[key] = a[key+"$"] = f = function() {return val}); + * + * f && (f.exName = key + "$") && (f.exClazz = a); + * + * })(val); + */ + + return val; + } + + /** + * SwingJS method to parse the _at_code and create a new version of the + * annotation. Note that it is unncessary to use Proxy, because we can directly + * insert the method in JavaScript + * + * @param qname + * @param atData + * @param declaringClass + * @return + */ + public static Annotation createAnnotation(String qname, String atData) { + try { + + Class annotationClass = null; + try { + annotationClass = (Class) Class.forName(qname); + } catch (NoClassDefFoundError e) { + throw new TypeNotPresentException(qname, e); + } + AnnotationType type = null; + type = AnnotationType.getInstance(annotationClass); + Map values = getJSValues(atData); + Map> memberTypes = type.memberTypes(); + Map memberValues = new LinkedHashMap(type.memberDefaults()); + // In SwingJS, we can instantiate the Annotation directly -- no need for proxy + Annotation a = annotationClass.newInstance(); + for (Entry entry : memberValues.entrySet()) { + String key = entry.getKey(); + if (values.containsKey(key)) { + memberValues.put(key, setLocalAnnotationValue(a, key, values.get(key), memberTypes.get(key))); + } + Object val = memberValues.get(key); + /** + * @j2sNative + * + * a[key] = a[key + "$"] = (function(val){ return function(){return val}})(val); + */ + } + aliasJS(a, "annotationType$", "annotationType"); + aliasJS(a, "equals$", "equals"); + aliasJS(a, "toString$", "toString"); + aliasJS(a, "hexCode$", "hexCode"); + + /** + * @j2sNative + * + * + */ + return a;//annotationForMap(annotationClass, memberValues); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | NoSuchMethodException | SecurityException e) { + throw new TypeNotPresentException(qname + "(" + atData + ")", e); + } + + } + + private static void aliasJS(Object a, String newName, String oldName) { + /** + * @j2sNative a[newName] = a[oldName]; + */ + + } + + private static Object boxValue(Object val, Class type) { + @SuppressWarnings("unused") + String name = type.getName(); + /** + * @j2sNative switch(name) { case "int": val = new Integer(val); break; case + * "short": val = new Short(val); break; case "long": val = new + * Long(val); break; case "float": val = new Float(val); break; case + * "double": val = new Double(val); break; case "byte": val = new + * Byte(val); break; case "boolean": val = val && val != "false" ? + * Boolean.TRUE : Boolean.FALSE; break; } + */ + return val; + } + + static Map, Annotation> parseAnnotations(String name, Class c, boolean isMethod) { + Object[][] __ANN_REC__ = getAnnotations(name, c.$clazz$, isMethod); + if (__ANN_REC__ == null) + return Collections.EMPTY_MAP; + // Raw data is in the form of C$.__ANN__, which has one record per annotated + // type, field, or method + Map, Annotation> result = new LinkedHashMap, Annotation>(); + // note that qnames will be one longer than atData after fixing + String[] qnames = getQNames(__ANN_REC__); + String[] atData = getAtCodes(__ANN_REC__); + for (int i = atData.length; --i >= 0;) { + Annotation a = createAnnotation(qnames[i], atData[i]); + if (a != null) { + Class klass = a.getClass(); + if ( + // AnnotationType.getInstance(klass).retention() == RetentionPolicy.RUNTIME && + result.put(klass, a) != null) { + throw new AnnotationFormatError("Duplicate annotation for class: " + klass + ": " + a); + } + } + } + return result; + } + + public static Class typeForString(String name, boolean allowNull) { + String s = name; + int dim = 0; + while (s.endsWith("[]")) { + dim++; + s = s.substring(0, s.length() - 2); + } + Class c = null; + if (s.indexOf(".") < 0) { + c = Class.getPrimitiveOrStringClass(s); + } + try { + if (c == null) + c = Class.forName(s); + if (dim > 0) { + while (--dim >= 0) { + c = Array.newInstance(c, 0).getClass(); + } + } + return c; + } catch (ClassNotFoundException e) { + if (allowNull) + return null; + // TODO Auto-generated catch block + e.printStackTrace(); + try { + throw e; + } catch (ClassNotFoundException e1) { + } + } + return null; + } + + @SuppressWarnings("unused") + public static Class[] getDeclaredClasses(Object clazz) { + String myName = /** @j2sNative clazz.__CLASS_NAME__ || */null; + // C$.$classes$=[['Singleton',8],['Test_Class_Inner',8],['B',0],['C',8]] + Object[][] data = /** @j2sNative clazz.$classes$ || */ null; + Class[] classes = new Class[0]; + if (data != null) { + for (int i = 0; i < data.length; i++) { + String name = (String) data[i][0]; + int modifiers = /** @j2sNative data[i][1] || */0; + Class cl = typeForString(myName + "." + name, false); + cl._setModifiers(modifiers); + /** @j2sNative classes.push(cl); */ + } + } + return classes; + } + + @SuppressWarnings("null") + public static Class[] guessMethodParameterTypes(String signature) { + String[] args = /** @j2sNative signature.split("$")|| */ + null; + Class[] classes = new Class[args.length - 1]; + if (args.length > 1) { + for (int i = 0; i < classes.length; i++) { + String param = args[i + 1]; + String arrays = ""; + int len = param.length(); + while (len > 1 && param.endsWith("A")) { + arrays += "[]"; + param = param.substring(0, --len); + } + param += arrays; + // in case we have __ initially due to non-packaged classes being put in package _. + param = param.substring(0, 1) + param.substring(1).replace('_', '.'); + classes[i] = typeForString(param, true); + if (classes[i] == null) { + classes[i] = Object.class; + System.err.println("JSAnnotationObject - method parameter typing failed for " + signature); + } + } + } + return classes; + } + } + + /** + * Parses the annotations described by the specified byte array. resolving + * constant references in the specified constant pool. The array must contain an + * array of annotations as described in the RuntimeVisibleAnnotations_attribute: + * + * u2 num_annotations; annotation annotations[num_annotations]; + * @param isMethod TODO + * @param __ANN__ + * + * @throws AnnotationFormatError if an annotation is found to be malformed. + */ + public static Map, Annotation> parseAnnotations(String name, Class c, boolean isMethod) { + return JSAnnotationObject.parseAnnotations(name, c, isMethod); + } + + /** + * Returns an annotation of the given type backed by the given member -> value + * map. + */ + public static Annotation annotationForMap(final Class type, + final Map memberValues) { + return (Annotation) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, + new AnnotationInvocationHandler(type, memberValues)); + } + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + + public static Annotation[] toArray(Map, Annotation> annotations) { + return annotations.values().toArray(EMPTY_ANNOTATION_ARRAY); + } + + public static Annotation[] getEmptyAnnotationArray() { + return EMPTY_ANNOTATION_ARRAY; + } + + +} diff --git a/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationSupport.java b/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationSupport.java new file mode 100644 index 000000000..d0b35d3ba --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationSupport.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2012, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.reflect.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.AnnotationFormatError; +import java.lang.annotation.Repeatable; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import sun.misc.JavaLangAccess; + +public final class AnnotationSupport { + private static final JavaLangAccess LANG_ACCESS = sun.misc.SharedSecrets.getJavaLangAccess(); + + /** + * Finds and returns all annotations in {@code annotations} matching + * the given {@code annoClass}. + * + * Apart from annotations directly present in {@code annotations} this + * method searches for annotations inside containers i.e. indirectly + * present annotations. + * + * The order of the elements in the array returned depends on the iteration + * order of the provided map. Specifically, the directly present annotations + * come before the indirectly present annotations if and only if the + * directly present annotations come before the indirectly present + * annotations in the map. + * + * @param annotations the {@code Map} in which to search for annotations + * @param annoClass the type of annotation to search for + * + * @return an array of instances of {@code annoClass} or an empty + * array if none were found + */ + public static A[] getDirectlyAndIndirectlyPresent( + Map, Annotation> annotations, + Class annoClass) { + List result = new ArrayList(); + + @SuppressWarnings("unchecked") + A direct = (A) annotations.get(annoClass); + if (direct != null) + result.add(direct); + + A[] indirect = getIndirectlyPresent(annotations, annoClass); + if (indirect != null && indirect.length != 0) { + boolean indirectFirst = direct == null || + containerBeforeContainee(annotations, annoClass); + + result.addAll((indirectFirst ? 0 : 1), Arrays.asList(indirect)); + } + + @SuppressWarnings("unchecked") + A[] arr = (A[]) Array.newInstance(annoClass, result.size()); + return result.toArray(arr); + } + + /** + * Finds and returns all annotations matching the given {@code annoClass} + * indirectly present in {@code annotations}. + * + * @param annotations annotations to search indexed by their types + * @param annoClass the type of annotation to search for + * + * @return an array of instances of {@code annoClass} or an empty array if no + * indirectly present annotations were found + */ + private static A[] getIndirectlyPresent( + Map, Annotation> annotations, + Class annoClass) { + + Repeatable repeatable = annoClass.getDeclaredAnnotation(Repeatable.class); + if (repeatable == null) + return null; // Not repeatable -> no indirectly present annotations + + Class containerClass = repeatable.value(); + + Annotation container = annotations.get(containerClass); + if (container == null) + return null; + + // Unpack container + A[] valueArray = getValueArray(container); + checkTypes(valueArray, container, annoClass); + + return valueArray; + } + + + /** + * Figures out if conatiner class comes before containee class among the + * keys of the given map. + * + * @return true if container class is found before containee class when + * iterating over annotations.keySet(). + */ + private static boolean containerBeforeContainee( + Map, Annotation> annotations, + Class annoClass) { + + Class containerClass = + annoClass.getDeclaredAnnotation(Repeatable.class).value(); + + for (Class c : annotations.keySet()) { + if (c == containerClass) return true; + if (c == annoClass) return false; + } + + // Neither containee nor container present + return false; + } + + + /** + * Finds and returns all associated annotations matching the given class. + * + * The order of the elements in the array returned depends on the iteration + * order of the provided maps. Specifically, the directly present annotations + * come before the indirectly present annotations if and only if the + * directly present annotations come before the indirectly present + * annotations in the relevant map. + * + * @param declaredAnnotations the declared annotations indexed by their types + * @param decl the class declaration on which to search for annotations + * @param annoClass the type of annotation to search for + * + * @return an array of instances of {@code annoClass} or an empty array if none were found. + */ + public static A[] getAssociatedAnnotations( + Map, Annotation> declaredAnnotations, + Class decl, + Class annoClass) { + Objects.requireNonNull(decl); + + // Search declared + A[] result = getDirectlyAndIndirectlyPresent(declaredAnnotations, annoClass); + + // Search inherited + if(AnnotationType.getInstance(annoClass).isInherited()) { + Class superDecl = decl.getSuperclass(); + while (result.length == 0 && superDecl != null) { + result = getDirectlyAndIndirectlyPresent(LANG_ACCESS.getDeclaredAnnotationMap(superDecl), annoClass); + superDecl = superDecl.getSuperclass(); + } + } + + return result; + } + + + /* Reflectively invoke the values-method of the given annotation + * (container), cast it to an array of annotations and return the result. + */ + private static A[] getValueArray(Annotation container) { + try { + // According to JLS the container must have an array-valued value + // method. Get the AnnotationType, get the "value" method and invoke + // it to get the content. + + Class containerClass = container.annotationType(); + AnnotationType annoType = AnnotationType.getInstance(containerClass); + if (annoType == null) + throw invalidContainerException(container, null); + + Method m = annoType.members().get("value"); + if (m == null) + throw invalidContainerException(container, null); + + m.setAccessible(true); + + // This will erase to (Annotation[]) but we do a runtime cast on the + // return-value in the method that call this method. + @SuppressWarnings("unchecked") + A[] values = (A[]) m.invoke(container); + + return values; + + } catch (IllegalAccessException | // couldn't loosen security + IllegalArgumentException | // parameters doesn't match + InvocationTargetException | // the value method threw an exception + ClassCastException e) { + + throw invalidContainerException(container, e); + + } + } + + + private static AnnotationFormatError invalidContainerException(Annotation anno, + Throwable cause) { + return new AnnotationFormatError( + anno + " is an invalid container for repeating annotations", + cause); + } + + + /* Sanity check type of all the annotation instances of type {@code annoClass} + * from {@code container}. + */ + private static void checkTypes(A[] annotations, + Annotation container, + Class annoClass) { + for (A a : annotations) { + if (!annoClass.isInstance(a)) { + throw new AnnotationFormatError( + String.format("%s is an invalid container for " + + "repeating annotations of type: %s", + container, annoClass)); + } + } + } +} diff --git a/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationType.java b/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationType.java new file mode 100644 index 000000000..8f41eaaa4 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/reflect/annotation/AnnotationType.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.reflect.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents an annotation type at run time. Used to type-check annotations + * and apply member defaults. + * + * @author Josh Bloch + * @since 1.5 + */ +public class AnnotationType { + /** + * Member name -> type mapping. Note that primitive types + * are represented by the class objects for the corresponding wrapper + * types. This matches the return value that must be used for a + * dynamic proxy, allowing for a simple isInstance test. + */ + private final Map> memberTypes; + + /** + * Member name -> default value mapping. + */ + private final Map memberDefaults; + + /** + * Member name -> Method object mapping. This (and its assoicated + * accessor) are used only to generate AnnotationTypeMismatchExceptions. + */ + private final Map members; + + /** + * The retention policy for this annotation type. + */ + private final RetentionPolicy retention; + + /** + * Whether this annotation type is inherited. + */ + private final boolean inherited; + + /** + * Returns an AnnotationType instance for the specified annotation type. + * + * @throw IllegalArgumentException if the specified class object for + * does not represent a valid annotation type + */ + public static AnnotationType getInstance( + Class annotationClass) + { + AnnotationType result = annotationClass.getAnnotationType(); + if (result == null) { + result = new AnnotationType(annotationClass); + annotationClass.setAnnotationResult(result); + } + return result; + } + + /** + * Sole constructor. + * + * @param annotationClass the class object for the annotation type + * @throw IllegalArgumentException if the specified class object for + * does not represent a valid annotation type + */ + private AnnotationType(final Class annotationClass) { + if (!annotationClass.isAnnotation()) + throw new IllegalArgumentException("Not an annotation type"); + + Method[] methods = annotationClass.getDeclaredMethods(); + + memberTypes = new HashMap>(methods.length+1, 1.0f); + memberDefaults = new HashMap(0); + members = new HashMap(methods.length+1, 1.0f); + + for (Method method : methods) { + String name = method.getName(); + Class type = method.getReturnType(); + memberTypes.put(name, type);//invocationHandlerReturnType(type)); + members.put(name, method); + + Object defaultValue = method.getDefaultValue(); + if (defaultValue != null) + memberDefaults.put(name, defaultValue); + } + + // Initialize retention, & inherited fields. Special treatment + // of the corresponding annotation types breaks infinite recursion. +// if (annotationClass != Retention.class && +// annotationClass != Inherited.class) { +//TODO JavaLangAccess jla = sun.misc.SharedSecrets.getJavaLangAccess(); +// Map, Annotation> metaAnnotations = +// AnnotationParser.parseSelectAnnotations( +// jla.getRawClassAnnotations(annotationClass), +// jla.getConstantPool(annotationClass), +// annotationClass, +// Retention.class, Inherited.class +// ); +// Retention ret = (Retention) metaAnnotations.get(Retention.class); +// retention = (ret == null ? RetentionPolicy.CLASS : ret.value()); +// inherited = metaAnnotations.containsKey(Inherited.class); +// } +// else { + retention = RetentionPolicy.RUNTIME; + inherited = false; +// } + } + + /** + * Returns the type that must be returned by the invocation handler + * of a dynamic proxy in order to have the dynamic proxy return + * the specified type (which is assumed to be a legal member type + * for an annotation). + */ + public static Class invocationHandlerReturnType(Class type) { + // No! That is not how it works. + // Translate primitives to wrappers +// if (type == byte.class) +// return Byte.class; +// if (type == char.class) +// return Character.class; +// if (type == double.class) +// return Double.class; +// if (type == float.class) +// return Float.class; +// if (type == int.class) +// return Integer.class; +// if (type == long.class) +// return Long.class; +// if (type == short.class) +// return Short.class; +// if (type == boolean.class) +// return Boolean.class; + + // Otherwise, just return declared type + return type; + } + + /** + * Returns member types for this annotation type + * (member name -> type mapping). + */ + public Map> memberTypes() { + return memberTypes; + } + + /** + * Returns members of this annotation type + * (member name -> associated Method object mapping). + */ + public Map members() { + return members; + } + + /** + * Returns the default values for this annotation type + * (Member name -> default value mapping). + */ + public Map memberDefaults() { + return memberDefaults; + } + + /** + * Returns the retention policy for this annotation type. + */ + public RetentionPolicy retention() { + return retention; + } + + /** + * Returns true if this this annotation type is inherited. + */ + public boolean isInherited() { + return inherited; + } + + /** + * For debugging. + */ + public String toString() { + return "Annotation Type:\n" + + " Member types: " + memberTypes + "\n" + + " Member defaults: " + memberDefaults + "\n" + + " Retention policy: " + retention + "\n" + + " Inherited: " + inherited; + } + + +} diff --git a/sources/net.sf.j2s.java.core/src/sun/reflect/misc/ConstructorUtil.java b/sources/net.sf.j2s.java.core/src/sun/reflect/misc/ConstructorUtil.java new file mode 100644 index 000000000..022e22505 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/reflect/misc/ConstructorUtil.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.reflect.misc; + +import java.lang.reflect.Constructor; + +public final class ConstructorUtil { + + private ConstructorUtil() { + } + + public static Constructor getConstructor(Class cls, Class[] params) + throws NoSuchMethodException { +// ReflectUtil.checkPackageAccess(cls); + return cls.getConstructor(params); + } + + public static Constructor[] getConstructors(Class cls) { + // ReflectUtil.checkPackageAccess(cls); + return cls.getConstructors(); + } +} diff --git a/sources/net.sf.j2s.java.core/src/sun/reflect/misc/FieldUtil.java b/sources/net.sf.j2s.java.core/src/sun/reflect/misc/FieldUtil.java new file mode 100644 index 000000000..3530caeb3 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/reflect/misc/FieldUtil.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2005, 2011, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.reflect.misc; + +import java.lang.reflect.Field; + +/* + * Create a trampoline class. + */ +public final class FieldUtil { + + private FieldUtil() { + } + + public static Field getField(Class cls, String name) + throws NoSuchFieldException { +// ReflectUtil.checkPackageAccess(cls); + return cls.getField(name); + } + + public static Field[] getFields(Class cls) { +// ReflectUtil.checkPackageAccess(cls); + return cls.getFields(); + } + + public static Field[] getDeclaredFields(Class cls) { +// ReflectUtil.checkPackageAccess(cls); + return cls.getDeclaredFields(); + } +} diff --git a/sources/net.sf.j2s.java.core/src/sun/reflect/misc/MethodUtil.java b/sources/net.sf.j2s.java.core/src/sun/reflect/misc/MethodUtil.java new file mode 100644 index 000000000..6c2aacda8 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/reflect/misc/MethodUtil.java @@ -0,0 +1,414 @@ +/* + * Copyright (c) 2005, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.reflect.misc; + +import java.security.AllPermission; +import java.security.AccessController; +import java.security.PermissionCollection; +import java.security.SecureClassLoader; +import java.security.PrivilegedExceptionAction; +import java.security.CodeSource; +import java.io.InputStream; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import sun.misc.IOUtils; +import swingjs.JSUtil; + + +class Trampoline { + static { + if (Trampoline.class.getClassLoader() == null) { + throw new Error( + "Trampoline must not be defined by the bootstrap classloader"); + } + } + + private static void ensureInvocableMethod(Method m) + throws InvocationTargetException + { + Class clazz = m.getDeclaringClass(); + if (clazz.equals(AccessController.class) || + clazz.equals(Method.class) || + clazz.getName().startsWith("java.lang.invoke.")) + throw new InvocationTargetException( + new UnsupportedOperationException("invocation not supported")); + } + + private static Object invoke(Method m, Object obj, Object[] params) + throws InvocationTargetException, IllegalAccessException + { + ensureInvocableMethod(m); + return m.invoke(obj, params); + } +} + +/* + * Create a trampoline class. + */ +public final class MethodUtil extends SecureClassLoader { + private static final String MISC_PKG = "sun.reflect.misc."; + private static final String TRAMPOLINE = MISC_PKG + "Trampoline"; + private static final Method bounce = getTrampoline(); + + private MethodUtil() { + super(); + } + + public static Method getMethod(Class cls, String name, Class[] args) + throws NoSuchMethodException { +// ReflectUtil.checkPackageAccess(cls); + return cls.getMethod(name, args); + } + + public static Method[] getMethods(Class cls) { +// ReflectUtil.checkPackageAccess(cls); + return cls.getMethods(); + } + + /* + * Discover the public methods on public classes + * and interfaces accessible to any caller by calling + * Class.getMethods() and walking towards Object until + * we're done. + */ + public static Method[] getPublicMethods(Class cls) { + // compatibility for update release +// if (System.getSecurityManager() == null) { + return cls.getMethods(); +// } +// Map sigs = new HashMap(); +// while (cls != null) { +// boolean done = getInternalPublicMethods(cls, sigs); +// if (done) { +// break; +// } +// getInterfaceMethods(cls, sigs); +// cls = cls.getSuperclass(); +// } +// return sigs.values().toArray(new Method[sigs.size()]); + } + + /* + * Process the immediate interfaces of this class or interface. + */ + private static void getInterfaceMethods(Class cls, + Map sigs) { + JSUtil.notImplemented(null); + Class[] intfs = cls.getInterfaces(); + for (int i=0; i < intfs.length; i++) { + Class intf = intfs[i]; + boolean done = getInternalPublicMethods(intf, sigs); + if (!done) { + getInterfaceMethods(intf, sigs); + } + } + } + + /* + * + * Process the methods in this class or interface + */ + private static boolean getInternalPublicMethods(Class cls, + Map sigs) { + Method[] methods = null; + try { + /* + * This class or interface is non-public so we + * can't use any of it's methods. Go back and + * try again with a superclass or superinterface. + */ +// if (!Modifier.isPublic(cls.getModifiers())) { +// return false; +// } +// if (!ReflectUtil.isPackageAccessible(cls)) { +// return false; +// } + + methods = cls.getMethods(); + } catch (SecurityException se) { + return false; + } + + /* + * Check for inherited methods with non-public + * declaring classes. They might override and hide + * methods from their superclasses or + * superinterfaces. + */ + boolean done = true; +// for (int i=0; i < methods.length; i++) { +// Class dc = methods[i].getDeclaringClass(); +// if (!Modifier.isPublic(dc.getModifiers())) { +// done = false; +// break; +// } +// } + + if (done) { + /* + * We're done. Spray all the methods into + * the list and then we're out of here. + */ + for (int i=0; i < methods.length; i++) { + addMethod(sigs, methods[i]); + } + } else { + /* + * Simulate cls.getDeclaredMethods() by + * stripping away inherited methods. + */ + for (int i=0; i < methods.length; i++) { + Class dc = methods[i].getDeclaringClass(); + if (cls.equals(dc)) { + addMethod(sigs, methods[i]); + } + } + } + return done; + } + + private static void addMethod(Map sigs, Method method) { + Signature signature = new Signature(method); + if (!sigs.containsKey(signature)) { + sigs.put(signature, method); + } else if (!method.getDeclaringClass().isInterface()){ + /* + * Superclasses beat interfaces. + */ + Method old = sigs.get(signature); + if (old.getDeclaringClass().isInterface()) { + sigs.put(signature, method); + } + } + } + + /** + * A class that represents the unique elements of a method that will be a + * key in the method cache. + */ + private static class Signature { + private String methodName; + private Class[] argClasses; + + private volatile int hashCode = 0; + + Signature(Method m) { + this.methodName = m.getName(); + this.argClasses = m.getParameterTypes(); + } + + public boolean equals(Object o2) { + if (this == o2) { + return true; + } + Signature that = (Signature)o2; + if (!(methodName.equals(that.methodName))) { + return false; + } + if (argClasses.length != that.argClasses.length) { + return false; + } + for (int i = 0; i < argClasses.length; i++) { + if (!(argClasses[i] == that.argClasses[i])) { + return false; + } + } + return true; + } + + /** + * Hash code computed using algorithm suggested in + * Effective Java, Item 8. + */ + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 37 * result + methodName.hashCode(); + if (argClasses != null) { + for (int i = 0; i < argClasses.length; i++) { + result = 37 * result + ((argClasses[i] == null) ? 0 : + argClasses[i].hashCode()); + } + } + hashCode = result; + } + return hashCode; + } + } + + + /* + * Bounce through the trampoline. + */ + public static Object invoke(Method m, Object obj, Object[] params) + throws InvocationTargetException, IllegalAccessException { + try { + return bounce.invoke(null, new Object[] {m, obj, params}); + } catch (InvocationTargetException ie) { + Throwable t = ie.getCause(); + + if (t instanceof InvocationTargetException) { + throw (InvocationTargetException)t; + } else if (t instanceof IllegalAccessException) { + throw (IllegalAccessException)t; + } else if (t instanceof RuntimeException) { + throw (RuntimeException)t; + } else if (t instanceof Error) { + throw (Error)t; + } else { + throw new Error("Unexpected invocation error", t); + } + } catch (IllegalAccessException iae) { + // this can't happen + throw new Error("Unexpected invocation error", iae); + } + } + + private static Method getTrampoline() { + try { + return AccessController.doPrivileged( + new PrivilegedExceptionAction() { + public Method run() throws Exception { + Class t = getTrampolineClass(); + Class[] types = { + Method.class, Object.class, Object[].class + }; + Method b = t.getDeclaredMethod("invoke", types); + b.setAccessible(true); + return b; + } + }); + } catch (Exception e) { + throw new InternalError("bouncer cannot be found", e); + } + } + + + protected synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + // First, check if the class has already been loaded + ReflectUtil.checkPackageAccess(name); + Class c = findLoadedClass(name); + if (c == null) { + try { + c = findClass(name); + } catch (ClassNotFoundException e) { + // Fall through ... + } + if (c == null) { + c = getParent().loadClass(name); + } + } + if (resolve) { + resolveClass(c); + } + return c; + } + + + protected Class findClass(final String name) + throws ClassNotFoundException + { + if (!name.startsWith(MISC_PKG)) { + throw new ClassNotFoundException(name); + } + String path = name.replace('.', '/').concat(".class"); + URL res = getResource(path); + if (res != null) { + try { + return defineClass(name, res); + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } else { + throw new ClassNotFoundException(name); + } + } + + + /* + * Define the proxy classes + */ + private Class defineClass(String name, URL url) throws IOException { + byte[] b = getBytes(url); + CodeSource cs = new CodeSource(null, (java.security.cert.Certificate[])null); + if (!name.equals(TRAMPOLINE)) { + throw new IOException("MethodUtil: bad name " + name); + } + return defineClass(name, b, 0, b.length, cs); + } + + + /* + * Returns the contents of the specified URL as an array of bytes. + */ + private static byte[] getBytes(URL url) throws IOException { + URLConnection uc = url.openConnection(); + if (uc instanceof java.net.HttpURLConnection) { + java.net.HttpURLConnection huc = (java.net.HttpURLConnection) uc; + int code = huc.getResponseCode(); + if (code >= java.net.HttpURLConnection.HTTP_BAD_REQUEST) { + throw new IOException("open HTTP connection failed."); + } + } + int len = uc.getContentLength(); + InputStream in = new BufferedInputStream(uc.getInputStream()); + + byte[] b; + try { + b = IOUtils.readFully(in, len, true); + } finally { + in.close(); + } + return b; + } + + + protected PermissionCollection getPermissions(CodeSource codesource) + { + PermissionCollection perms = super.getPermissions(codesource); + perms.add(new AllPermission()); + return perms; + } + + private static Class getTrampolineClass() { + try { + return Class.forName(TRAMPOLINE, true, new MethodUtil()); + } catch (ClassNotFoundException e) { + } + return null; + } + +} diff --git a/sources/net.sf.j2s.java.core/src/sun/reflect/misc/ReflectUtil.java b/sources/net.sf.j2s.java.core/src/sun/reflect/misc/ReflectUtil.java new file mode 100644 index 000000000..31f010ce7 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/sun/reflect/misc/ReflectUtil.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2005, 2013, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package sun.reflect.misc; + +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import sun.reflect.Reflection; +import sun.security.util.SecurityConstants; + +public final class ReflectUtil { + + private ReflectUtil() { + } + + public static Class forName(String name) + throws ClassNotFoundException { +// checkPackageAccess(name); + return Class.forName(name); + } + + public static Object newInstance(Class cls) + throws InstantiationException, IllegalAccessException { +// checkPackageAccess(cls); + return cls.newInstance(); + } + + /* + * Reflection.ensureMemberAccess is overly-restrictive + * due to a bug. We awkwardly work around it for now. + */ + public static void ensureMemberAccess(Class currentClass, + Class memberClass, + Object target, + int modifiers) + throws IllegalAccessException + { +// if (target == null && Modifier.isProtected(modifiers)) { +// int mods = modifiers; +// mods = mods & (~Modifier.PROTECTED); +// mods = mods | Modifier.PUBLIC; +// +// /* +// * See if we fail because of class modifiers +// */ +// Reflection.ensureMemberAccess(currentClass, +// memberClass, +// target, +// mods); +// try { +// /* +// * We're still here so class access was ok. +// * Now try with default field access. +// */ +// mods = mods & (~Modifier.PUBLIC); +// Reflection.ensureMemberAccess(currentClass, +// memberClass, +// target, +// mods); +// /* +// * We're still here so access is ok without +// * checking for protected. +// */ + return; +// } catch (IllegalAccessException e) { +// /* +// * Access failed but we're 'protected' so +// * if the test below succeeds then we're ok. +// */ +// if (isSubclassOf(currentClass, memberClass)) { +// return; +// } else { +// throw e; +// } +// } +// } else { +// Reflection.ensureMemberAccess(currentClass, +// memberClass, +// target, +// modifiers); +// } + } + +// private static boolean isSubclassOf(Class queryClass, +// Class ofClass) +// { +// while (queryClass != null) { +// if (queryClass == ofClass) { +// return true; +// } +// queryClass = queryClass.getSuperclass(); +// } +// return false; +// } +// + /** + * Does a conservative approximation of member access check. Use this if + * you don't have an actual 'userland' caller Class/ClassLoader available. + * This might be more restrictive than a precise member access check where + * you have a caller, but should never allow a member access that is + * forbidden. + * + * @param m the {@code Member} about to be accessed + */ + public static void conservativeCheckMemberAccess(Member m) throws SecurityException{ +// final SecurityManager sm = System.getSecurityManager(); +// if (sm == null) +// return; +// +// // Check for package access on the declaring class. +// // +// // In addition, unless the member and the declaring class are both +// // public check for access declared member permissions. +// // +// // This is done regardless of ClassLoader relations between the {@code +// // Member m} and any potential caller. +// +// final Class declaringClass = m.getDeclaringClass(); +// +// checkPackageAccess(declaringClass); +// +// if (Modifier.isPublic(m.getModifiers()) && +// Modifier.isPublic(declaringClass.getModifiers())) +// return; +// +// // Check for declared member access. +// sm.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION); + } + + /** + * Checks package access on the given class. + * + * If it is a {@link Proxy#isProxyClass(java.lang.Class)} that implements + * a non-public interface (i.e. may be in a non-restricted package), + * also check the package access on the proxy interfaces. + */ + public static void checkPackageAccess(Class clazz) { +// checkPackageAccess(clazz.getName()); +// if (isNonPublicProxyClass(clazz)) { +// checkProxyPackageAccess(clazz); +// } + } + + /** + * Checks package access on the given classname. + * This method is typically called when the Class instance is not + * available and the caller attempts to load a class on behalf + * the true caller (application). + */ + public static void checkPackageAccess(String name) { +// SecurityManager s = System.getSecurityManager(); +// if (s != null) { +// String cname = name.replace('/', '.'); +// if (cname.startsWith("[")) { +// int b = cname.lastIndexOf('[') + 2; +// if (b > 1 && b < cname.length()) { +// cname = cname.substring(b); +// } +// } +// int i = cname.lastIndexOf('.'); +// if (i != -1) { +// s.checkPackageAccess(cname.substring(0, i)); +// } +// } + } + + public static boolean isPackageAccessible(Class clazz) { +// try { +// checkPackageAccess(clazz); +// } catch (SecurityException e) { +// return false; +// } + return true; + } + + // Returns true if p is an ancestor of cl i.e. class loader 'p' can + // be found in the cl's delegation chain + private static boolean isAncestor(ClassLoader p, ClassLoader cl) { + ClassLoader acl = cl; + do { + acl = acl.getParent(); + if (p == acl) { + return true; + } + } while (acl != null); + return false; + } + + /** + * Returns true if package access check is needed for reflective + * access from a class loader 'from' to classes or members in + * a class defined by class loader 'to'. This method returns true + * if 'from' is not the same as or an ancestor of 'to'. All code + * in a system domain are granted with all permission and so this + * method returns false if 'from' class loader is a class loader + * loading system classes. On the other hand, if a class loader + * attempts to access system domain classes, it requires package + * access check and this method will return true. + */ + public static boolean needsPackageAccessCheck(ClassLoader from, ClassLoader to) { +// if (from == null || from == to) + return false; +// +// if (to == null) +// return true; +// +// return !isAncestor(from, to); + } + + /** + * Check package access on the proxy interfaces that the given proxy class + * implements. + * + * @param clazz Proxy class object + */ + public static void checkProxyPackageAccess(Class clazz) { +// SecurityManager s = System.getSecurityManager(); +// if (s != null) { +// // check proxy interfaces if the given class is a proxy class +// if (Proxy.isProxyClass(clazz)) { +// for (Class intf : clazz.getInterfaces()) { +// checkPackageAccess(intf); +// } +// } +// } + } + + /** + * Access check on the interfaces that a proxy class implements and throw + * {@code SecurityException} if it accesses a restricted package from + * the caller's class loader. + * + * @param ccl the caller's class loader + * @param interfaces the list of interfaces that a proxy class implements + */ + public static void checkProxyPackageAccess(ClassLoader ccl, + Class... interfaces) + { +// SecurityManager sm = System.getSecurityManager(); +// if (sm != null) { +// for (Class intf : interfaces) { +// ClassLoader cl = intf.getClassLoader(); +// if (needsPackageAccessCheck(ccl, cl)) { +// checkPackageAccess(intf); +// } +// } +// } + } + + // Note that bytecode instrumentation tools may exclude 'sun.*' + // classes but not generated proxy classes and so keep it in com.sun.* + public static final String PROXY_PACKAGE = "com.sun.proxy"; + + /** + * Test if the given class is a proxy class that implements + * non-public interface. Such proxy class may be in a non-restricted + * package that bypasses checkPackageAccess. + */ + public static boolean isNonPublicProxyClass(Class cls) { + String name = cls.getName(); + int i = name.lastIndexOf('.'); + String pkg = (i != -1) ? name.substring(0, i) : ""; + return Proxy.isProxyClass(cls) && !pkg.equals(PROXY_PACKAGE); + } + + /** + * Check if the given method is a method declared in the proxy interface + * implemented by the given proxy instance. + * + * @param proxy a proxy instance + * @param method an interface method dispatched to a InvocationHandler + * + * @throws IllegalArgumentException if the given proxy or method is invalid. + */ + public static void checkProxyMethod(Object proxy, Method method) { + // check if it is a valid proxy instance + if (proxy == null || !Proxy.isProxyClass(proxy.getClass())) { + throw new IllegalArgumentException("Not a Proxy instance"); +} + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalArgumentException("Can't handle static method"); + } + + Class c = method.getDeclaringClass(); + if (c == Object.class) { + String name = method.getName(); + if (name.equals("hashCode") || name.equals("equals") || name.equals("toString")) { + return; + } + } + + if (isSuperInterface(proxy.getClass(), c)) { + return; + } + + // disallow any method not declared in one of the proxy intefaces + throw new IllegalArgumentException("Can't handle: " + method); + } + + private static boolean isSuperInterface(Class c, Class intf) { + for (Class i : c.getInterfaces()) { + if (i == intf) { + return true; + } + if (isSuperInterface(i, intf)) { + return true; + } + } + return false; + } + + /** + * Checks if {@code Class cls} is a VM-anonymous class + * as defined by {@link sun.misc.Unsafe#defineAnonymousClass} + * (not to be confused with a Java Language anonymous inner class). + */ + public static boolean isVMAnonymousClass(Class cls) { + return cls.getName().indexOf("/") >= 0; + } +} diff --git a/sources/net.sf.j2s.java.core/src/swingjs/JSAppletViewer.java b/sources/net.sf.j2s.java.core/src/swingjs/JSAppletViewer.java index dbf87f51f..87d032c8c 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/JSAppletViewer.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/JSAppletViewer.java @@ -56,7 +56,7 @@ * * The basic start up in JavaScript involves: * - * Clazz.loadClass("swingjs.JSAppletViewer"); var _appletViewer = new + * Clazz.load("swingjs.JSAppletViewer"); var _appletViewer = new * JSAppletViewer(viewerOptions); _appletViewer.start(); * * where viewerOptions holds critical information needed to create this applet diff --git a/sources/net.sf.j2s.java.core/src/swingjs/JSAudio.java b/sources/net.sf.j2s.java.core/src/swingjs/JSAudio.java index cc542a007..4dc8b56ae 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/JSAudio.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/JSAudio.java @@ -231,7 +231,7 @@ public byte[] createWaveData(byte[] data, AudioFormat af) // 0x0001 WAVE_FORMAT_PCM PCM // 0x0003 WAVE_FORMAT_IEEE_FLOAT IEEE float // 0x0006 WAVE_FORMAT_ALAW 8-bit ITU-T G.711 A-law - // 0x0007 WAVE_FORMAT_MULAW 8-bit ITU-T G.711 µ-law + // 0x0007 WAVE_FORMAT_MULAW 8-bit ITU-T G.711 �-law // byte[] b; int fmt = FORMAT_UNSUPPORTED; @@ -362,6 +362,7 @@ public static AudioInputStream getAudioInputStream(ByteArrayInputStream stream) return new JSAudioInputStream(stream, getAudioFormatForStreamOrBytes(stream, null), -1); } + @SuppressWarnings("unused") private static AudioFormat getAudioFormatForStreamOrBytes( ByteArrayInputStream stream, byte[] header) throws UnsupportedAudioFileException { @@ -392,8 +393,8 @@ private static AudioFormat getAudioFormatForStreamOrBytes( stream.read(header); } sampleSizeInBits = 8; - int pt = BC.bytesToInt(header, 4, true); - int length = BC.bytesToInt(header, 8, true); + //int pt = BC.bytesToInt(header, 4, true); + //int length = BC.bytesToInt(header, 8, true); int enc = BC.bytesToInt(header, 12, true); frameRate = sampleRate = BC.bytesToInt(header, 16, true); channels = BC.bytesToInt(header, 20, true); diff --git a/sources/net.sf.j2s.java.core/src/swingjs/JSFileSystem.java b/sources/net.sf.j2s.java.core/src/swingjs/JSFileSystem.java index 0b83ca809..af734dcb0 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/JSFileSystem.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/JSFileSystem.java @@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -87,39 +88,40 @@ public void release() throws IOException { public static class JSFileChannel extends FileChannel { - // Used by FileInputStream.getChannel() and RandomAccessFile.getChannel() - public static FileChannel open(FileDescriptor fd, String path, boolean readable, boolean writable, - Object parent) { - return new JSFileChannel(fd, path, readable, writable, false, parent); + private FileDescriptor fd; + private Object parent; // possibly a FileOutputStream + private JSPath path; + private JSByteChannel bc; + + public static JSFileChannel open(FileDescriptor fd, String path, boolean readable, boolean writable, + Object parent) throws IOException { + // Used by FileInputStream.getChannel() and RandomAccessFile.getChannel() + return open(fd, path, readable, writable, false, parent); } - // Used by FileOutputStream.getChannel - public static FileChannel open(FileDescriptor fd, String path, boolean readable, boolean writable, - boolean append, Object parent) { + public static JSFileChannel open(FileDescriptor fd, String path, boolean readable, boolean writable, + boolean append, Object parent) throws IOException { + // Used by FileOutputStream.getChannel return new JSFileChannel(fd, path, readable, writable, append, parent); } - public JSFileChannel(Path path, Set options, FileAttribute[] attrs) { + public JSFileChannel(Path path, Set options, FileAttribute[] attrs) throws IOException { + // from JSFileSystemProvider this.fd = null; this.parent = null; this.path= (JSPath) path; - try { - bc = new JSByteChannel(this.path, options, attrs); - } catch (FileAlreadyExistsException e) { - } +// try { + bc = new JSByteChannel(null, this.path, options, attrs); +// } catch (FileAlreadyExistsException e) { +// } } - private FileDescriptor fd; - private Object parent; // possibly a FileOutputStream - private JSPath path; - private JSByteChannel bc; - private JSFileChannel(FileDescriptor fd, String path, boolean readable, boolean writable, boolean append, - Object parent) { + Object parent) throws IOException { this.fd = fd; this.parent = parent; this.path = (JSPath) new File(path).toPath(); - + this.path.setIsTempFile(fd._isTempFile()); Set options = new HashSet<>(); if (readable) options.add(StandardOpenOption.READ); @@ -130,7 +132,7 @@ private JSFileChannel(FileDescriptor fd, String path, boolean readable, boolean if (append) options.add(StandardOpenOption.APPEND); try { - bc = new JSByteChannel(this.path, options, NO_ATTRIBUTES); + bc = new JSByteChannel(fd, this.path, options, NO_ATTRIBUTES); } catch (FileAlreadyExistsException e) { } // this.nd = new FileDispatcherImpl(append); @@ -243,6 +245,45 @@ protected void implCloseChannel() throws IOException { bc.close(); } + // From RandomAccessFile + public void seek(long pos) { + bc.seek(pos); + } + + public int read() { + return bc.read(); + } + + public int readBytes(byte[] b, int off, int len) { + return bc.readBytes(b, off, len); + } + + public void write(int b) { + bc.write(b); + } + + public void writeBytes(byte[] b, int off, int length) { + if (length > 0) + bc.writeBytes(b, off, length); + } + + public void setLength(long newLength) { + bc.setLength(newLength); + } + + public void doSave() { + bc.doSave = true; + } + + public int available() { + return (int) (bc.len - bc.pos); + } + + public long skip(long n) { + bc.seek(bc.pos + n); + return bc.pos; + } + } public static class JSFileAttributes implements BasicFileAttributes { @@ -336,29 +377,41 @@ public T value() { public static class JSByteChannel implements SeekableByteChannel, WritableByteChannel, ReadableByteChannel, BasicFileAttributeView { + /** + * flag to indicate that a RAF has opened a file that already has bytes for + * writing but never written anything. + */ + boolean doSave = false; + long tCreate, tMod, tAccess; private JSPath path; private byte[] 秘bytes; private BufferedInputStream bis; protected int pos, len; private JSFileAttribute[] attrs; - private boolean open, append, write, delete; + private boolean open, append, write, read, delete; private JSFileAttributes fsAttrs; - public JSByteChannel(JSPath path, Set options, FileAttribute[] attrs) - throws FileAlreadyExistsException { + public JSByteChannel(FileDescriptor fd, JSPath path, Set options, FileAttribute[] attrs) + throws IOException { this.path = path; this.attrs = (JSFileAttribute[]) attrs; // APPEND, CREATE, CREATE_NEW, DELETE_ON_CLOSE, // READ, TRUNCATE_EXISTING, WRITE, - + read = options.contains(StandardOpenOption.READ); write = options.contains(StandardOpenOption.WRITE); append = options.contains(StandardOpenOption.APPEND); delete = options.contains(StandardOpenOption.DELETE_ON_CLOSE); boolean truncate = options.contains(StandardOpenOption.TRUNCATE_EXISTING); boolean create = options.contains(StandardOpenOption.CREATE); boolean createNew = options.contains(StandardOpenOption.CREATE_NEW); + if (read && write) { + 秘bytes = getBytes(); + if (秘bytes == null) { + 秘bytes = new byte[len = 0]; + } + } if (append) { write = true; pos = len = getBytes().length; @@ -369,8 +422,10 @@ public JSByteChannel(JSPath path, Set options, FileAttribu if (b != null) throw new FileAlreadyExistsException(path.name); } - path.秘bytes = null; - JSUtil.cacheFileData(path.name, null); + path.秘bytes = (read ? getBytes() : null); + // from FileOutputStream + setPosLen(fd); + JSUtil.cacheFileData(path.name, path.秘bytes); } else if (truncate) { path.秘bytes = null; } @@ -379,7 +434,72 @@ public JSByteChannel(JSPath path, Set options, FileAttribu } else { getBytes(); } + } else { + // readonly + if (getBytes() == null) + throw new FileNotFoundException(); + // from FileInputStream + setPosLen(fd); + } + open = true; + } + + // for RandomAccessFile + + private void setPosLen(FileDescriptor fd) { + if (fd != null) { + pos = fd._getPos(); + len = fd._getLen(); + } + } + + public void setLength(long newLength) { + if (len == newLength) + return; + len = (int) newLength; + ensureLength(len); + if (pos > len) + pos = len; + } + + private void ensureLength(int len) { + 秘bytes = Arrays.copyOf(秘bytes, len); + } + + public void seek(long pos) { + this.pos = (int) pos; + } + + public int read() { + return (pos >= len ? -1 : 秘bytes[pos++]); + } + + public int readBytes(byte[] b, int off, int len) { + if (len == 0) + return 0; + len = Math.min(this.len - pos, len); + if (len < 0) + return -1; + System.arraycopy(秘bytes, pos, b, off, len); + return len; + } + + public void write(int b) { + if (pos >= len) { + ensureLength(len + 8192); + len++; + } + 秘bytes[pos++] = (byte) b; + } + + + public void writeBytes(byte[] b, int off, int length) { + if (pos + length >= len) { + ensureLength(pos + Math.max(length << 1, 8192)); + len = pos + length; } + System.arraycopy(b, off, 秘bytes, pos, length); + pos += length; } @@ -390,16 +510,21 @@ public boolean isOpen() { @Override public void close() throws IOException { + if (!open) + return; open = false; if (delete) { 秘bytes = null; JSUtil.cacheFileData(path.name, null); } else if (write) { + if (!doSave) + return; if (len < 秘bytes.length) 秘bytes = Arrays.copyOf(秘bytes, len); path.秘bytes = 秘bytes; JSUtil.cacheFileData(path.name, 秘bytes); - JSUtil.saveFile(path.name, 秘bytes, null, null); + if (!path.isTempFile) + JSUtil.saveFile(path.name, 秘bytes, null, null); } } @@ -453,8 +578,9 @@ private byte[] getBytes() { 秘bytes = path.秘bytes; if (秘bytes == null) { 秘bytes = JSUtil.getFileAsBytes(path.toString()); - len = 秘bytes.length; } + if (秘bytes != null) + len = 秘bytes.length; return 秘bytes; } @@ -542,6 +668,15 @@ public class JSPath implements Path { private String[] nameArray; public byte[] 秘bytes; + private boolean isTempFile; + + public boolean isTempFile() { + return isTempFile; + } + + public void setIsTempFile(boolean isTempFile) { + this.isTempFile = isTempFile; + } private String[] getNameArray() { if (nameArray == null) @@ -551,6 +686,8 @@ private String[] getNameArray() { } public JSPath(String name, JSFileSystem jsFileSystem) { + while (name.startsWith("././")) + name = name.substring(3); this.name = name; this.fileSystem = jsFileSystem; } @@ -674,7 +811,7 @@ public Path relativize(Path other) { String[] a = ((JSPath) getParent()).getNameArray(); String[] b = ((JSPath) other.getParent()).getNameArray(); - String[] c = new String[Math.max(a.length, b.length)]; +// String[] c = new String[Math.max(a.length, b.length)]; int min = Math.min(a.length, b.length); int firstDiff = 0; for (; firstDiff < min; firstDiff++) { @@ -758,6 +895,11 @@ public void setAttribute(String attribute, Object value) { } + public void set(byte[] bytes, File file) { + 秘bytes = bytes; + setIsTempFile(file instanceof JSTempFile); + } + } public static class JSFileSystemProvider extends FileSystemProvider { @@ -797,11 +939,11 @@ public Path getPath(URI uri) { @Override public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) throws IOException { - return new JSByteChannel((JSPath) path, options, attrs); + return new JSByteChannel(null, (JSPath) path, options, attrs); } @Override - public FileChannel newFileChannel(Path path, Set options, + public JSFileChannel newFileChannel(Path path, Set options, FileAttribute... attrs) throws IOException { return new JSFileChannel((JSPath) path, options, attrs); } @@ -820,7 +962,7 @@ public void createDirectory(Path dir, FileAttribute... attrs) throws IOExcept @Override public void delete(Path path) throws IOException { - ni(); + JSUtil.removeCachedFileData(path.toString()); } @Override diff --git a/sources/net.sf.j2s.java.core/src/swingjs/JSTempFile.java b/sources/net.sf.j2s.java.core/src/swingjs/JSTempFile.java index 501c287db..83f57a68a 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/JSTempFile.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/JSTempFile.java @@ -26,7 +26,7 @@ public void deleteOnExit() { public void cacheBytes() { JSUtil.cacheFileData(path, 秘bytes); } - + public boolean setBytes(Object isOrBytes) { if (isOrBytes instanceof InputStream) { 秘bytes = /** diff --git a/sources/net.sf.j2s.java.core/src/swingjs/JSUtil.java b/sources/net.sf.j2s.java.core/src/swingjs/JSUtil.java index ce7ae4cee..eb32f3994 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/JSUtil.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/JSUtil.java @@ -28,10 +28,6 @@ public class JSUtil { - public JSUtil() { - System.out.println("JSUtil initialized"); - } - static { boolean j2sdebug = false; J2SInterface j2sself = null; @@ -46,6 +42,7 @@ public JSUtil() { } debugging = j2sdebug; J2S = j2sself; + System.out.println("swingjs.JSUtil initialized;debug=" + j2sdebug); } /** @@ -65,19 +62,31 @@ private static Map getFileCache() { return fileCache; } - private static Object getCachedFileData(String path) { + public static Object getCachedFileData(String path) { return (useCache && fileCache != null ? - fileCache.get(path) : null); + fileCache.get(fixCachePath(path)) : null); } + /** + * Set the cache to Boolean.FALSE to indicate that we have checked this + * @param path + * @return + */ + public static Object removeCachedFileData(String path) { + return (useCache && fileCache != null ? + fileCache.put(fixCachePath(path), Boolean.FALSE) : null); + } + + /** * This could be a simple String, a javajs.util.SB, or unsigned or signed bytes * depending upon the browser and the file type. * - * It will not be cached, but it might come from a cache; + * It will not be cached, but it might come from a cache; * - * @param uriOrJSFile - * @return + * @param uriOrJSFile File or URL or URI or String + * + * @return may be byte[], String, or javajs.util.SB if found; Boolean FALSE if not found */ @SuppressWarnings("unused") private static Object getFileContents(Object uriOrJSFile, boolean asBytes) { @@ -97,9 +106,11 @@ private static Object getFileContents(Object uriOrJSFile, boolean asBytes) { if (!uri.startsWith("/")) uri = "/" + uri; uri = "http://." + uri; + // Note that SwingJS will convert this to https if necessary. } - if (data == null) + if (data == null) { data = getCachedFileData(uri); + } if (data == null && !uri.startsWith("./")) { // Java applications may use "./" here try { @@ -108,11 +119,33 @@ private static Object getFileContents(Object uriOrJSFile, boolean asBytes) { } catch (Exception e) { // bypasses AjaxURLConnection data = J2S.getFileData(uri, null, false, asBytes); + if (data == null) + removeCachedFileData(uri); } } return data; } + private static String fixCachePath(String uri) { + int pt; + if (uri.startsWith("./")) + uri = "/" + uri; + if (uri.startsWith("https:/")) + uri = uri.substring(7); + if (uri.startsWith("http:/")) + uri = uri.substring(6); + uri = uri.replace("//", "/"); + while ((pt = uri.indexOf("/././")) >= 0) { + // https://././xxx --> /./xxx + uri = uri.substring(0, pt) + uri.substring(pt + 2); + } + if (uri.startsWith("/")) + uri = uri.substring(1); + if (uri.startsWith("./")) + uri = uri.substring(2); + return uri; + } + /** * Regardless of how returned by Jmol._getFileContents(), * this method ensures that we get a String. @@ -122,11 +155,37 @@ private static Object getFileContents(Object uriOrJSFile, boolean asBytes) { */ public static String getFileAsString(String filename) { Object data = getFileContents(filename, false); - return ensureString(data); + return ensureString(data); + } + + /** + * Transform byte[], SB, or InputStream data to String + * @param data + * @return String data or null + */ + static String ensureString(Object data) { + if (data == null) + return null; + if (data instanceof byte[]) + return Rdr.bytesToUTF8String((byte[]) data); + if (data instanceof String || data instanceof SB) + return data.toString(); + if (data instanceof InputStream) + return Rdr.streamToUTF8String(new BufferedInputStream((InputStream) data)); + return null; } + /** + * Ensure byte[] or null + * + * @param file + * @param checkNotFound + * @return + */ public static byte[] getFileAsBytes(Object file, boolean checkNotFound) { byte[] data = getFileAsBytes(file); + if (data == null) + return null; if (checkNotFound) { if (data.length == 0) return null; @@ -145,14 +204,18 @@ public static byte[] getFileAsBytes(Object file, boolean checkNotFound) { } /** + * Standard call for getting file contents. + * * Regardless of how returned by Jmol.getFileContents(), * this method ensures that we get signed bytes. * * @param filename - * @return + * @return byte[] or null */ public static byte[] getFileAsBytes(Object file) { Object data = getFileContents(file, true); + if (data == null || data == Boolean.FALSE) + return null; byte[] b = null; if (data instanceof byte[]) b = (byte[]) data; @@ -180,28 +243,34 @@ public static boolean haveCachedResource(String resourceName, boolean isJavaPath * * @param resourceName * @param isJavaPath - * @param doProcess - * evaluate JS or load CSS + * @param doProcess evaluate JS or load CSS * @return the resource as a string */ - public static String getJavaResource(String resourceName, boolean isJavaPath, - boolean doCache, boolean doProcess) { + public static String getJavaResource(String resourceName, boolean isJavaPath, boolean doCache, boolean doProcess) { System.out.println("JSUtil getting Java resource " + resourceName); String path = J2S.getResourcePath(resourceName, isJavaPath); if (path == null) return null; Object data = getCachedFileData(path); - if (data == null - && (data = J2S.getFileData(path, null, false, false)) != null - && useCache && doCache) - cacheFileData(path, data); + if (data == Boolean.FALSE) + return null; + if (data == null) { + data = J2S.getFileData(path, null, false, false); + if (data == null) { + if (useCache && doCache) { + removeCachedFileData(path); + } + } else if (useCache && doCache) { + cacheFileData(path, data); + } + } String sdata = ensureString(data); boolean ok = (sdata != null && sdata.indexOf("[Exception") != 0); - System.out.println("Processing " + path + " [" - + (ok ? "" + sdata.length() : sdata) + "]"); - return (!ok ? null : !doProcess ? sdata - : path.endsWith(".css") ? processCSS(sdata, path) : path - .endsWith(".js") ? processJS(sdata, resourceName) : sdata); + System.out.println("Processing " + path + " [" + (ok ? "" + sdata.length() : sdata) + "]"); + return (!ok ? null + : !doProcess ? sdata + : path.endsWith(".css") ? processCSS(sdata, path) + : path.endsWith(".js") ? processJS(sdata, resourceName) : sdata); } public static InputStream getCachedResourceAsStream(String name) { @@ -231,7 +300,14 @@ public static void cacheFileData(String path, Object data) { System.out.println("JSUtil releasing cached bytes for " + path); getFileCache().remove(path); } else { - System.out.println("JSUtil caching bytes for " + path); + String count = "?"; + if (data instanceof byte[]) + count = ""+ ((byte[]) data).length; + else if (data instanceof String) + count = "" + ((String) data).length(); + path = fixCachePath(path); + if (!getFileCache().containsKey(path)) + System.out.println("JSUtil caching " + count + " bytes for " + path); getFileCache().put(path, data); } } @@ -313,18 +389,6 @@ static String processJS(String js, String resourceName) { return js; } - static String ensureString(Object data) { - if (data == null) - return null; - if (data instanceof byte[]) - return Rdr.bytesToUTF8String((byte[]) data); - if (data instanceof String || data instanceof SB) - return data.toString(); - if (data instanceof InputStream) - return Rdr.streamToUTF8String(new BufferedInputStream((InputStream) data)); - return null; - } - /** * Sets window.jQuery.$ = window.jQuery, so that we can call jQuery.$ * diff --git a/sources/net.sf.j2s.java.core/src/swingjs/image/GifImageWriter.java b/sources/net.sf.j2s.java.core/src/swingjs/image/GifImageWriter.java index 6591c96ff..e5acd9721 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/image/GifImageWriter.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/image/GifImageWriter.java @@ -18,7 +18,7 @@ * @see javax.imageio.spi.ImageWriterSpi * */ -public class GifImageWriter extends ImageWriter { +public class GifImageWriter extends JSImageWriter { public GifImageWriter() { super(); params.put("type", "GIF"); diff --git a/sources/net.sf.j2s.java.core/src/swingjs/image/JSImageWriter.java b/sources/net.sf.j2s.java.core/src/swingjs/image/JSImageWriter.java new file mode 100644 index 000000000..ff77a6afc --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/swingjs/image/JSImageWriter.java @@ -0,0 +1,273 @@ +package swingjs.image; + +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Map; + +import javax.imageio.IIOImage; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; + +import javajs.api.GenericImageEncoder; +import javajs.util.OC; +import javajs.util.PT; +import swingjs.JSToolkit; +import swingjs.JSUtil; +import swingjs.api.Interface; + +public abstract class JSImageWriter extends ImageWriter { + + @Override + public abstract void write(IIOMetadata streamMetadata, + IIOImage image, + ImageWriteParam param) throws IOException; + + @Override + public abstract ImageWriteParam getDefaultWriteParam(); + + + protected Map params = new Hashtable(); + + + protected Locale locale; + + /** + * Create an ImageWriter that will be set with "image" and "type" later + * + */ + public JSImageWriter() { + super(null); + // allows passing a Hashtable with all information at once. + } + + public void setImage(RenderedImage im) { + params.put("image", (BufferedImage) im); + } + + + protected void setMetaData(Object streamMetadata) { + // not implemented + } + + public boolean write(RenderedImage im, String fileName, OutputStream out) { + try { + // OC used here to pass to javajs + OC outputChannel = null; + if (out instanceof OC) { + outputChannel = (OC) out; + fileName = null; + } + if (outputChannel == null) + outputChannel = new OC().setParams(null, fileName, false, (OutputStream) out); + params.put("outputChannel", outputChannel); + write(null, new IIOImage(im, null, null), null); + } catch (IOException e) { + return false; + } + return true; + } + + @Override + public void write(IIOImage image) throws IOException { + params.put("image", image.getRenderedImage()); + try { + writeImage(params); + } catch (Exception e) { + if (e instanceof IOException) + throw new IOException(((IOException) e).getMessage()); + e.printStackTrace(); + } + } + + protected boolean writeToFileOrStream(String fileName, OutputStream out) { + OC outputChannel = (OC) params.get("outputChannel"); + if (out != null) { + outputChannel = new OC(); + outputChannel.setParams(null, fileName, false, out); + params.put("outputChannel", outputChannel); + } + if (outputChannel != null) + fileName = outputChannel.getFileName(); + try { + Object o = writeImage(params); + if (o instanceof String) { + System.out.println("ImageWriter: " + o); + return false; + } + if (out == null && o != null) + JSUtil.saveFile(fileName, o, (String) params.get("mimeType"), null); + } catch (Exception e) { + return false; + } + return true; + + } + + /** + * Write a PNG, GIF, JPG, or JPG64 image to a file or output channel (javajs.util.OC) or return its bytes + * starting with an image or int[] RGB buffer; allows for appending application data with a predefined + * application-specific 9-character prefix and any byte data desired. + * + * @param params + * {type:"PNG|GIF|JPEG|JPG|JPG64", transparentColor:int, fileName, image, quality + * rbgbuf+width+height, pngAppData+pngAppPrefix} + * @return String error message or byte[] if fileName and outputChannel are null or null if successful + * @throws Exception + */ + public Object writeImage(Map params) throws Exception { + BufferedImage image = (BufferedImage) params.get("image"); + if (image == null && !params.containsKey("rgbbuf")) + throw new IllegalArgumentException("image == null!"); + byte[] bytes = null; + String errMsg = null; + String type = (String) params.get("type"); + if (type == null) + type = "png"; + type = type.toUpperCase(); + String fileName = (String) params.get("fileName"); + String software = (String) params.get("software"); + if (software == null) { + software = "SwingJS"; + } + String comment = (String) params.get("comment"); + if (comment == null) + params.put("comment", software); + OC out = (OC) params.get("outputChannel"); + boolean asBytes = (out == null && fileName == null); + boolean closeChannel = (out == null && fileName != null); + boolean isOK = false; + try { + if (out == null + && (out = getOutputChannel(fileName)) == null) + return "ERROR: canceled"; + fileName = out.getFileName(); + params.put("date", JSToolkit.getDateFormat("8601")); + if (type.startsWith("JP")) { + type = PT.rep(type, "E", ""); + if (type.equals("JPG64")) { + params.put("outputChannelTemp", getOutputChannel(null)); + } + } else if (type.equals("PDF")) { + } else if (type.startsWith("PNG")) { + // if (stateData != null) { + // params.put("pngAppData", stateData); + // params.put("pngAppPrefix", "Jmol Type"); + // } + } + String[] errRet = new String[1]; + isOK = createTheImage(image, type, out, params, errRet); + if (closeChannel) + out.closeChannel(); + if (isOK) { + if (asBytes) + bytes = out.toByteArray(); + } else { + errMsg = errRet[0]; + } + } finally { + if (bytes != null || out != null) + params.put("byteCount", Integer.valueOf(bytes != null ? bytes.length + : isOK ? out.getByteCount() : -1)); + } + return (errMsg == null ? bytes : errMsg); + } + + OC getOutputChannel(String fileName) throws IOException { + @SuppressWarnings("resource") + OC outputChannel = new OC(); + return outputChannel.setParams(null, fileName, false, null); + } + + /** + * @param objImage + * @param type + * @param out + * @param params + * @param errRet + * @return byte array if needed + */ + private boolean createTheImage(BufferedImage image, String type, OC out, + Map params, String[] errRet) { + params.put("mimeType", "image/" + type.toLowerCase()); + type = type.substring(0, 1) + type.substring(1).toLowerCase(); + GenericImageEncoder ie = (GenericImageEncoder) Interface.getInstance( + "javajs.img." + type + "Encoder", true); + if (ie == null) { + errRet[0] = "Image encoder type " + type + " not available"; + return false; + } + boolean doClose = true; + int[] pixels; + int w, h; + try { + + // finish up with the image parameters, + // either image or rgbbuf+width+height + + if (image == null) { + w = ((Integer) params.get("width")).intValue(); + h = ((Integer) params.get("height")).intValue(); + pixels = (int[]) params.get("rgbbuf"); + } else { + w = image.getWidth(); + h = image.getHeight(); + pixels = image.getRangeRGB(0, 0, w, h, new int[w * h], 0, w); + } + params.put("imageWidth", Integer.valueOf(w)); + params.put("imageHeight", Integer.valueOf(h)); + params.put("imagePixels", pixels); + + // now create the image using these parameters + + doClose = ie.createImage(type, out, params); + + } catch (Exception e) { + errRet[0] = e.toString(); + out.cancel(); + doClose = true; + } finally { + if (doClose) + out.closeChannel(); + } + return (errRet[0] == null); + } + + @Override + public Locale getLocale() { + return locale; + } + + + @Override + public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { + // TODO Auto-generated method stub + return null; + } + + @Override + public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) { + // TODO Auto-generated method stub + return null; + } + + @Override + public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) { + // TODO Auto-generated method stub + return null; + } + + @Override + public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) { + // TODO Auto-generated method stub + return null; + } + + +} + diff --git a/sources/net.sf.j2s.java.core/src/swingjs/image/JpgImageWriter.java b/sources/net.sf.j2s.java.core/src/swingjs/image/JpgImageWriter.java index 3ae521378..bac6e8568 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/image/JpgImageWriter.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/image/JpgImageWriter.java @@ -18,7 +18,7 @@ * @see javax.imageio.spi.ImageWriterSpi * */ -public class JpgImageWriter extends ImageWriter { +public class JpgImageWriter extends JSImageWriter { public JpgImageWriter() { super(); params.put("type", "JPG"); diff --git a/sources/net.sf.j2s.java.core/src/swingjs/image/PngImageWriter.java b/sources/net.sf.j2s.java.core/src/swingjs/image/PngImageWriter.java index 8c9230c58..2d20b2d65 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/image/PngImageWriter.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/image/PngImageWriter.java @@ -4,6 +4,7 @@ import javax.imageio.IIOImage; import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.metadata.IIOMetadata; @@ -18,7 +19,7 @@ * @see javax.imageio.spi.ImageWriterSpi * */ -public class PngImageWriter extends ImageWriter { +public class PngImageWriter extends JSImageWriter { public PngImageWriter() { super(); params.put("type", "PNG"); @@ -37,4 +38,5 @@ public ImageWriteParam getDefaultWriteParam() { return new PngImageWriteParam(null); } + } diff --git a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Deflate.java b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Deflate.java index fd92426a8..c424eb0b7 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Deflate.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Deflate.java @@ -1492,20 +1492,20 @@ int deflateParams(int _level, int _strategy) { return err; } - int deflateSetDictionary(byte[] dictionary, int dictLength) { - int length = dictLength; - int index = 0; + int deflateSetDictionary(byte[] dictionary, int off, int len) { + int length = len; + int index = off; if (dictionary == null || status != INIT_STATE) return Z_STREAM_ERROR; - strm.checksum.update(dictionary, 0, dictLength); + strm.checksum.update(dictionary, off, len); if (length < MIN_MATCH) return Z_OK; if (length > w_size - MIN_LOOKAHEAD) { length = w_size - MIN_LOOKAHEAD; - index = dictLength - length; // use the tail of the dictionary + index = len - length; // use the tail of the dictionary } System.arraycopy(dictionary, index, window, 0, length); strstart = length; @@ -1534,7 +1534,7 @@ int deflate(int flush) { return Z_STREAM_ERROR; } - if (strm.next_out == null || (strm.next_in == null && strm.avail_in != 0) + if (strm.next_out == null || (strm.in == null && strm.avail_in != 0) || (status == FINISH_STATE && flush != Z_FINISH)) { strm.msg = z_errmsg[Z_NEED_DICT - (Z_STREAM_ERROR)]; return Z_STREAM_ERROR; @@ -1794,4 +1794,5 @@ public long getBytesRead() { public long getBytesWritten() { return strm.total_out; } + } diff --git a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Deflater.java b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Deflater.java index 51a1dbb1b..4c71f7e90 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Deflater.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Deflater.java @@ -36,137 +36,141 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING public class Deflater extends ZStream { - static final private int MAX_WBITS = 15; // 32K LZ77 window - - //static final private int DEF_WBITS=MAX_WBITS; - - // static final private int Z_NO_FLUSH=0; - // static final private int Z_PARTIAL_FLUSH=1; - // static final private int Z_SYNC_FLUSH=2; - // static final private int Z_FULL_FLUSH=3; - // static final private int Z_FINISH=4; - // - // static final private int MAX_MEM_LEVEL=9; - - //static final private int Z_OK = 0; - static final private int Z_STREAM_END = 1; - // static final private int Z_NEED_DICT=2; - // static final private int Z_ERRNO=-1; - static final private int Z_STREAM_ERROR = -2; - // static final private int Z_DATA_ERROR=-3; - // static final private int Z_MEM_ERROR=-4; - // static final private int Z_BUF_ERROR=-5; - // static final private int Z_VERSION_ERROR=-6; - - private boolean finished = false; - - /* - - public Deflater(int level) { - this(level, 0, false); - } - - public Deflater(int level, boolean nowrap) { - this(level, 0, nowrap); - } - - public Deflater(int level, int bits) { - this(level, bits, false); - } - */ - -/* - public Deflater(int level, int bits, int memlevel) { - super(); - init3(level, bits, memlevel); - //if (ret != Z_OK) - //throw new GZIPException(ret + ": " + msg); - } - public int init(int level) { - return init2(level, MAX_WBITS); - } - - public int init2(int level, int bits) { - return init3b(level, bits, false); - } - - - public int init2b(int level, boolean nowrap) { - return init3b(level, MAX_WBITS, nowrap); - } - - public int init3(int level, int bits, int memlevel) { - finished = false; - dstate = new Deflate(this); - return dstate.deflateInit3(level, bits, memlevel); - } - - -*/ - public Deflater init(int level, int bits, boolean nowrap) { - if (bits == 0) - bits = MAX_WBITS; - finished = false; - setAdler32(); - dstate = new Deflate(this); - dstate.deflateInit2(level, nowrap ? -bits : bits); - return this; - } - - @Override - public int deflate(int flush) { - if (dstate == null) { - return Z_STREAM_ERROR; - } - int ret = dstate.deflate(flush); - if (ret == Z_STREAM_END) - finished = true; - return ret; - } - - @Override - public int end() { - finished = true; - if (dstate == null) - return Z_STREAM_ERROR; - int ret = dstate.deflateEnd(); - dstate = null; - free(); - return ret; - } - - public int params(int level, int strategy) { - if (dstate == null) - return Z_STREAM_ERROR; - return dstate.deflateParams(level, strategy); - } - - public int setDictionary(byte[] dictionary, int dictLength) { - if (dstate == null) - return Z_STREAM_ERROR; - return dstate.deflateSetDictionary(dictionary, dictLength); - } - - @Override - public boolean finished() { - return finished; - } - - public void finish() { - // native use only? - - } - - public long getBytesRead() { - return dstate.getBytesRead(); - } - - public long getBytesWritten() { - return dstate.getBytesWritten(); - } - - // public int copy(Deflater src){ - // this.finished = src.finished; - // return Deflate.deflateCopy(this, src); - // } + static final private int MAX_WBITS = 15; // 32K LZ77 window + + // static final private int DEF_WBITS=MAX_WBITS; + + // static final private int Z_NO_FLUSH=0; + // static final private int Z_PARTIAL_FLUSH=1; + // static final private int Z_SYNC_FLUSH=2; + // static final private int Z_FULL_FLUSH=3; + // static final private int Z_FINISH=4; + // + // static final private int MAX_MEM_LEVEL=9; + + // static final private int Z_OK = 0; + static final private int Z_STREAM_END = 1; + // static final private int Z_NEED_DICT=2; + // static final private int Z_ERRNO=-1; + static final private int Z_STREAM_ERROR = -2; + // static final private int Z_DATA_ERROR=-3; + // static final private int Z_MEM_ERROR=-4; + // static final private int Z_BUF_ERROR=-5; + // static final private int Z_VERSION_ERROR=-6; + + private boolean finished = false; + + /* + * + * public Deflater(int level) { this(level, 0, false); } + * + * public Deflater(int level, boolean nowrap) { this(level, 0, nowrap); } + * + * public Deflater(int level, int bits) { this(level, bits, false); } + */ + + /* + * public Deflater(int level, int bits, int memlevel) { super(); init3(level, + * bits, memlevel); //if (ret != Z_OK) //throw new GZIPException(ret + ": " + + * msg); } public int init(int level) { return init2(level, MAX_WBITS); } + * + * public int init2(int level, int bits) { return init3b(level, bits, false); } + * + * + * public int init2b(int level, boolean nowrap) { return init3b(level, + * MAX_WBITS, nowrap); } + * + * public int init3(int level, int bits, int memlevel) { finished = false; + * dstate = new Deflate(this); return dstate.deflateInit3(level, bits, + * memlevel); } + * + * + */ + public Deflater init(int level, int bits, boolean nowrap) { + if (bits == 0) + bits = MAX_WBITS; + finished = false; + setAdler32(); + dstate = new Deflate(this); + dstate.deflateInit2(level, nowrap ? -bits : bits); + return this; + } + + public int deflate(byte[] buf, int off, int len, int flush) { + super.setOutput(buf, off, len); + long thisLen = getBytesWritten(); + deflate(flush); + long newLen = getBytesWritten(); + return (int) (newLen - thisLen); + } + + @Override + public int deflate(int flush) { + if (dstate == null) { + return Z_STREAM_ERROR; + } + int ret = dstate.deflate(flush); + if (ret == Z_STREAM_END) + finished = true; + return ret; + } + +// public boolean needsInput() { +// return avail_out == 0; +// } +// + @Override + public void end() { + finished = true; + if (dstate == null) + return; +// return Z_STREAM_ERROR; + int ret = dstate.deflateEnd(); + dstate = null; + free(); +// return ret; + } + + public int params(int level, int strategy) { + if (dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateParams(level, strategy); + } + + public int setDictionaryRet(byte[] dictionary, int off, int len) { + if (dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateSetDictionary(dictionary, off, len); + } + + @Override + public boolean finished() { + return finished; + } + + public void finish() { + // native use only? + + } + + public long getBytesRead() { + return dstate.getBytesRead(); + } + + public long getBytesWritten() { + return dstate.getBytesWritten(); + } + + public void reset() { + dstate.deflateReset(); + } + + public boolean needsInput() { + return avail_in <= 0; + } + + // public int copy(Deflater src){ + // this.finished = src.finished; + // return Deflate.deflateCopy(this, src); + // } } diff --git a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/DeflaterOutputStream.java b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/DeflaterOutputStream.java index 759f8e571..f141d70c9 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/DeflaterOutputStream.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/DeflaterOutputStream.java @@ -131,6 +131,10 @@ public void close() throws IOException { closed = true; } } + + public boolean isClosed() { + return closed; + } protected int deflate(int flush) throws IOException { deflater.setOutput(buffer, 0, buffer.length); @@ -170,11 +174,11 @@ public void flush() throws IOException { } public long getTotalIn() { - return deflater.getTotalIn(); + return deflater.getTotalInL(); } public long getTotalOut() { - return deflater.getTotalOut(); + return deflater.getTotalOutL(); } public void setSyncFlush(boolean syncFlush) { diff --git a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/InfBlocks.java b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/InfBlocks.java index 5a86852fa..e97a7457c 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/InfBlocks.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/InfBlocks.java @@ -144,7 +144,7 @@ int proc(int r){ int m; // bytes to end of window or read pointer // copy input/output information to locals (UPDATE macro restores) - {p=z.next_in_index;n=z.avail_in;b=bitb;k=bitk;} + {p=z.in_index;n=z.avail_in;b=bitb;k=bitk;} {q=write;m=(qn) t = n; if(t>m) t = m; - System.arraycopy(z.next_in, p, window, q, t); + System.arraycopy(z.in, p, window, q, t); p += t; n -= t; q += t; m -= t; if ((left -= t) != 0) @@ -285,12 +285,12 @@ int proc(int r){ else{ bitb=b; bitk=k; z.avail_in=n; - z.total_in+=p-z.next_in_index;z.next_in_index=p; + z.total_in+=p-z.in_index;z.in_index=p; write=q; return inflate_flush(r); } n--; - b|=(z.next_in[p++]&0xff)<> 24 @@ -631,13 +631,13 @@ int inflateSync() { if ((n = z.avail_in) == 0) return Z_BUF_ERROR; - p = z.next_in_index; + p = z.in_index; m = this.marker; // search while (n != 0 && m < 4) { - if (z.next_in[p] == mark[m]) { + if (z.in[p] == mark[m]) { m++; - } else if (z.next_in[p] != 0) { + } else if (z.in[p] != 0) { m = 0; } else { m = 4 - m; @@ -647,8 +647,8 @@ int inflateSync() { } // restore - z.total_in += p - z.next_in_index; - z.next_in_index = p; + z.total_in += p - z.in_index; + z.in_index = p; z.avail_in = n; this.marker = m; @@ -691,7 +691,7 @@ private int readBytes(int n, int r, int f) throws Return { z.avail_in--; z.total_in++; this.need = this.need - | ((z.next_in[z.next_in_index++] & 0xff) << ((n - need秘bytes) * 8)); + | ((z.in[z.in_index++] & 0xff) << ((n - need秘bytes) * 8)); need秘bytes--; } if (n == 2) { @@ -726,11 +726,11 @@ private int readString(int r, int f) throws Return { r = f; z.avail_in--; z.total_in++; - b = z.next_in[z.next_in_index]; + b = z.in[z.in_index]; if (b != 0) - tmp_string.write(z.next_in, z.next_in_index, 1); - z.checksum.update(z.next_in, z.next_in_index, 1); - z.next_in_index++; + tmp_string.write(z.in, z.in_index, 1); + z.checksum.update(z.in, z.in_index, 1); + z.in_index++; } while (b != 0); return r; } @@ -748,9 +748,9 @@ private int readBytes(int r, int f) throws Return { z.avail_in--; z.total_in++; //b = z.next_in[z.next_in_index]; - tmp_string.write(z.next_in, z.next_in_index, 1); - z.checksum.update(z.next_in, z.next_in_index, 1); - z.next_in_index++; + tmp_string.write(z.in, z.in_index, 1); + z.checksum.update(z.in, z.in_index, 1); + z.in_index++; this.need--; } return r; diff --git a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Inflater.java b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Inflater.java index 1cf11242a..7c6918635 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Inflater.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/Inflater.java @@ -77,12 +77,13 @@ public int inflate(int f){ } @Override - public int end(){ + public void end(){ //finished = true; - if(istate==null) return Z_STREAM_ERROR; - int ret=istate.inflateEnd(); + if(istate==null) return;// Z_STREAM_ERROR; + //int ret= + istate.inflateEnd(); // istate = null; - return ret; +// return ret; } public int sync(){ diff --git a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/InflaterInputStream.java b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/InflaterInputStream.java index db11d62db..c7f2ad879 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/InflaterInputStream.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/InflaterInputStream.java @@ -220,18 +220,18 @@ public synchronized void reset() throws IOException { } public long getTotalIn() { - return inflater.getTotalIn(); + return inflater.getTotalInL(); } public long getTotalOut() { - return inflater.getTotalOut(); + return inflater.getTotalOutL(); } public byte[] getAvailIn() { if (inflater.avail_in <= 0) return null; byte[] tmp = new byte[inflater.avail_in]; - System.arraycopy(inflater.next_in, inflater.next_in_index, tmp, 0, + System.arraycopy(inflater.in, inflater.in_index, tmp, 0, inflater.avail_in); return tmp; } diff --git a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/ZStream.java b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/ZStream.java index 4794c05f0..fa58d2f47 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/jzlib/ZStream.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/jzlib/ZStream.java @@ -41,9 +41,9 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * * ZStream * - * deprecated? Not for public use in the future. + * deprecated? Not for public use in the future. */ -abstract public class ZStream{ +abstract public class ZStream { // static final private int MAX_WBITS=15; // 32K LZ77 window // static final private int DEF_WBITS=MAX_WBITS; @@ -60,141 +60,127 @@ abstract public class ZStream{ // static final private int Z_STREAM_END=1; // static final private int Z_NEED_DICT=2; // static final private int Z_ERRNO=-1; - static final private int Z_STREAM_ERROR=-2; + static final private int Z_STREAM_ERROR = -2; // static final private int Z_DATA_ERROR=-3; // static final private int Z_MEM_ERROR=-4; // static final private int Z_BUF_ERROR=-5; // static final private int Z_VERSION_ERROR=-6; - byte[] next_in; // next input byte - int next_in_index; - protected int avail_in; // number of bytes available at next_in - protected long total_in; // total nb of input bytes read so far - - byte[] next_out; // next output byte should be put there - int next_out_index; - int avail_out; // remaining free space at next_out - protected long total_out; // total nb of bytes output so far - - String msg; - - Deflate dstate; - Inflate istate; - - int data_type; // best guess about the data type: ascii or binary - - Checksum checksum; - - void setAdler32() { - this.checksum=new Adler32(); - } - - /* - public int inflateInit(){ - return inflateInit(DEF_WBITS); - } - public int inflateInit(boolean nowrap){ - return inflateInit(DEF_WBITS, nowrap); - } - public int inflateInit(int w){ - return inflateInit(w, false); - } - - public int inflateInit(int w, boolean nowrap){ - istate=new Inflate(this); - return istate.inflateInit(nowrap?-w:w); - } - - public int inflateEnd(){ - if(istate==null) return Z_STREAM_ERROR; - int ret=istate.inflateEnd(); -// istate = null; - return ret; - } - - */ - - - int inflate(int f){ - if(istate==null) return Z_STREAM_ERROR; - return istate.inflate(f); - } - - - /* - - public int inflateSync(){ - if(istate == null) - return Z_STREAM_ERROR; - return istate.inflateSync(); - } - public int inflateSyncPoint(){ - if(istate == null) - return Z_STREAM_ERROR; - return istate.inflateSyncPoint(); - } - public int inflateSetDictionary(byte[] dictionary, int dictLength){ - if(istate == null) - return Z_STREAM_ERROR; - return istate.inflateSetDictionary(dictionary, dictLength); - } - public boolean inflateFinished(){ - return istate.mode==12; //DONE - } - - public int deflateInit(int level){ - return deflateInit(level, MAX_WBITS); - } - public int deflateInit(int level, boolean nowrap){ - return deflateInit(level, MAX_WBITS, nowrap); - } - public int deflateInit(int level, int bits){ - return deflateInit(level, bits, false); - } - public int deflateInit(int level, int bits, int memlevel){ - dstate=new Deflate(this); - return dstate.deflateInit3(level, bits, memlevel); - } - public int deflateInit(int level, int bits, boolean nowrap){ - dstate=new Deflate(this); - return dstate.deflateInit2(level, nowrap?-bits:bits); - } - - public int deflateEnd(){ - if(dstate==null) return Z_STREAM_ERROR; - int ret=dstate.deflateEnd(); - dstate=null; - return ret; - } - - public int deflateParams(int level, int strategy){ - if(dstate==null) return Z_STREAM_ERROR; - return dstate.deflateParams(level, strategy); - } - - public int deflateSetDictionary (byte[] dictionary, int dictLength){ - if(dstate == null) - return Z_STREAM_ERROR; - return dstate.deflateSetDictionary(dictionary, dictLength); - } - - */ - int deflate(int flush){ - if(dstate==null){ - return Z_STREAM_ERROR; - } - return dstate.deflate(flush); - } - - // Flush as much pending output as possible. All deflate() output goes - // through this function so some applications may wish to modify it - // to avoid allocating a large strm->next_out buffer and copying into it. - // (See also read_buf()). - void flush_pending(){ - int len=dstate.pending; - - if(len>avail_out) len=avail_out; - if(len==0) return; + byte[] in; // next input byte + int in_index; + int avail_in; // number of bytes available at next_in + protected long total_in; // total nb of input bytes read so far + + byte[] next_out; // next output byte should be put there + int next_out_index; + int avail_out; // remaining free space at next_out + long total_out; // total nb of bytes output so far + + String msg; + + Deflate dstate; + Inflate istate; + + int data_type; // best guess about the data type: ascii or binary + + Checksum checksum; + + void setAdler32() { + this.checksum = new Adler32(); + } + + /* + * public int inflateInit(){ return inflateInit(DEF_WBITS); } public int + * inflateInit(boolean nowrap){ return inflateInit(DEF_WBITS, nowrap); } public + * int inflateInit(int w){ return inflateInit(w, false); } + * + * public int inflateInit(int w, boolean nowrap){ istate=new Inflate(this); + * return istate.inflateInit(nowrap?-w:w); } + * + * public int inflateEnd(){ if(istate==null) return Z_STREAM_ERROR; int + * ret=istate.inflateEnd(); // istate = null; return ret; } + * + */ + + int inflate(int f) { + if (istate == null) + return Z_STREAM_ERROR; + return istate.inflate(f); + } + +// public int inflateSync(){ +// if(istate == null) +// return Z_STREAM_ERROR; +// return istate.inflateSync(); +// } +// public int inflateSyncPoint(){ +// if(istate == null) +// return Z_STREAM_ERROR; +// return istate.inflateSyncPoint(); +// } +// public int inflateSetDictionary(byte[] dictionary, int dictLength){ +// if(istate == null) +// return Z_STREAM_ERROR; +// return istate.inflateSetDictionary(dictionary, dictLength); +// } +// public boolean inflateFinished(){ +// return istate.mode==12; //DONE +// } +// +// public int deflateInit(int level){ +// return deflateInit(level, MAX_WBITS); +// } +// public int deflateInit(int level, boolean nowrap){ +// return deflateInit(level, MAX_WBITS, nowrap); +// } +// public int deflateInit(int level, int bits){ +// return deflateInit(level, bits, false); +// } +// public int deflateInit(int level, int bits, int memlevel){ +// dstate=new Deflate(this); +// return dstate.deflateInit3(level, bits, memlevel); +// } +// public int deflateInit(int level, int bits, boolean nowrap){ +// dstate=new Deflate(this); +// return dstate.deflateInit2(level, nowrap?-bits:bits); +// } +// +// public int deflateEnd(){ +// if(dstate==null) return Z_STREAM_ERROR; +// int ret=dstate.deflateEnd(); +// dstate=null; +// return ret; +// } +// +// public int deflateParams(int level, int strategy){ +// if(dstate==null) return Z_STREAM_ERROR; +// return dstate.deflateParams(level, strategy); +// } +// + public int deflateSetDictionary(byte[] dictionary, int off, int len) { + if (dstate == null) + return Z_STREAM_ERROR; + return dstate.deflateSetDictionary(dictionary, off, len); + } + + int deflate(int flush) { + if (dstate == null) { + return Z_STREAM_ERROR; + } + return dstate.deflate(flush); + } + + // Flush as much pending output as possible. All deflate() output goes + // through this function so some applications may wish to modify it + // to avoid allocating a large strm->next_out buffer and copying into it. + // (See also read_buf()). + void flush_pending() { + int len = dstate.pending; + + if (len > avail_out) + len = avail_out; + if (len == 0) + return; // if(dstate.pending_buf.length<=dstate.pending_out || // next_out.length<=next_out_index || @@ -205,156 +191,138 @@ void flush_pending(){ // System.out.println("avail_out="+avail_out); // } - System.arraycopy(dstate.pending_buf, dstate.pending_out, - next_out, next_out_index, len); - - next_out_index+=len; - dstate.pending_out+=len; - total_out+=len; - avail_out-=len; - dstate.pending-=len; - if(dstate.pending==0){ - dstate.pending_out=0; - } - } - - // Read a new buffer from the current input stream, update the adler32 - // and total number of bytes read. All deflate() input goes through - // this function so some applications may wish to modify it to avoid - // allocating a large strm->next_in buffer and copying from it. - // (See also flush_pending()). - int read_buf(byte[] buf, int start, int size) { - int len=avail_in; - - if(len>size) len=size; - if(len==0) return 0; - - avail_in-=len; - - if(dstate.wrap!=0) { - checksum.update(next_in, next_in_index, len); - } - System.arraycopy(next_in, next_in_index, buf, start, len); - next_in_index += len; - total_in += len; - return len; - } - - long getAdler(){ - return checksum.getValue(); - } - - void free(){ - next_in=null; - next_out=null; - msg=null; - } - - void setOutput(byte[] buf, int off, int len){ - next_out = buf; - next_out_index = off; - avail_out = len; - } - - void setInput(byte[] buf, int off, int len, boolean append){ - if(len<=0 && append && next_in!=null) return; - if(avail_in>0 && append){ - byte[] tmp = new byte[avail_in+len]; - System.arraycopy(next_in, next_in_index, tmp, 0, avail_in); - System.arraycopy(buf, off, tmp, avail_in, len); - next_in=tmp; - next_in_index=0; - avail_in+=len; - } - else{ - next_in=buf; - next_in_index=off; - avail_in=len; - } - } - - /* - public byte[] getNextIn(){ - return next_in; - } - - public void setNextIn(byte[] next_in){ - this.next_in = next_in; - } - - public int getNextInIndex(){ - return next_in_index; - } - - public void setNextInIndex(int next_in_index){ - this.next_in_index = next_in_index; - } - public void setAvailIn(int avail_in){ - this.avail_in = avail_in; - } - - public byte[] getNextOut(){ - return next_out; - } - - public void setNextOut(byte[] next_out){ - this.next_out = next_out; - } - - public int getNextOutIndex(){ - return next_out_index; - } - - public void setNextOutIndex(int next_out_index){ - this.next_out_index = next_out_index; - } - - public int getAvailOut(){ - return avail_out; - - } - - public void setAvailOut(int avail_out){ - this.avail_out = avail_out; - } - public String getMessage(){ - return msg; - } - -*/ - public int getAvailIn(){ - return avail_in; - } - - public long getTotalOut(){ - return total_out; - } - - public long getTotalIn(){ - return total_in; - } - - /** - * Those methods are expected to be override by Inflater and Deflater. - * In the future, they will become abstract methods. - * @return true or false - */ - abstract int end();//{ return Z_OK; } - abstract boolean finished();//{ return false; } - - /** - * added by Bob Hanson - * - * @param s - * @return UTF-8 byte array (or, for code points < 256, ISO-8859-1) - */ - public static byte[] getBytes(String s) { - try { - return s.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - // impossible - return null; - } - } - + System.arraycopy(dstate.pending_buf, dstate.pending_out, next_out, next_out_index, len); + + next_out_index += len; + dstate.pending_out += len; + total_out += len; + avail_out -= len; + dstate.pending -= len; + if (dstate.pending == 0) { + dstate.pending_out = 0; + } + } + + // Read a new buffer from the current input stream, update the adler32 + // and total number of bytes read. All deflate() input goes through + // this function so some applications may wish to modify it to avoid + // allocating a large strm->next_in buffer and copying from it. + // (See also flush_pending()). + public int read_buf(byte[] out, int start, int size) { + int len = avail_in; + + if (len > size) + len = size; + if (len == 0) + return 0; + + avail_in -= len; + + if (dstate.wrap != 0) { + checksum.update(in, in_index, len); + } + System.arraycopy(in, in_index, out, start, len); + in_index += len; + total_in += len; + return len; + } + + public long getAdlerL() { + return checksum.getValue(); + } + + void free() { + in = null; + next_out = null; + msg = null; + } + + void setOutput(byte[] buf, int off, int len) { + next_out = buf; + next_out_index = off; + avail_out = len; + } + + public void setInput(byte[] in, int off, int len, boolean append) { + if (len <= 0 && append && this.in != null) + return; + if (avail_in > 0 && append) { + byte[] tmp = new byte[avail_in + len]; + System.arraycopy(this.in, in_index, tmp, 0, avail_in); + System.arraycopy(in, off, tmp, avail_in, len); + this.in = tmp; + in_index = 0; + avail_in += len; + } else { + this.in = in; + in_index = off; + avail_in = len; + } + } + + /* + * public byte[] getNextIn(){ return next_in; } + * + * public void setNextIn(byte[] next_in){ this.next_in = next_in; } + * + * public int getNextInIndex(){ return next_in_index; } + * + * public void setNextInIndex(int next_in_index){ this.next_in_index = + * next_in_index; } public void setAvailIn(int avail_in){ this.avail_in = + * avail_in; } + * + * public byte[] getNextOut(){ return next_out; } + * + * public void setNextOut(byte[] next_out){ this.next_out = next_out; } + * + * public int getNextOutIndex(){ return next_out_index; } + * + * public void setNextOutIndex(int next_out_index){ this.next_out_index = + * next_out_index; } + * + * public int getAvailOut(){ return avail_out; + * + * } + * + * public void setAvailOut(int avail_out){ this.avail_out = avail_out; } public + * String getMessage(){ return msg; } + * + */ + public int getAvailIn() { + return avail_in; + } + + public long getTotalOutL() { + return total_out; + } + + public long getTotalInL() { + return total_in; + } + + /** + * Those methods are expected to be override by Inflater and Deflater. In the + * future, they will become abstract methods. + * + * @return true or false + */ + abstract void end();// { return Z_OK; } + + abstract boolean finished();// { return false; } + + /** + * added by Bob Hanson + * + * @param s + * @return UTF-8 byte array (or, for code points < 256, ISO-8859-1) + */ + public static byte[] getBytes(String s) { + try { + return s.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + // impossible + return null; + } + } + } diff --git a/sources/net.sf.j2s.java.core/src/swingjs/plaf/JSSliderUI.java b/sources/net.sf.j2s.java.core/src/swingjs/plaf/JSSliderUI.java index 54cee0f24..c745f6061 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/plaf/JSSliderUI.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/plaf/JSSliderUI.java @@ -168,7 +168,7 @@ public void setBackground(Color c) { } else { DOMNode node = (myScrollPaneUI == null && !paintTicks ? jqSlider : sliderTrack); DOMNode.setStyles(node, "background-color", JSToolkit.getCSSColor(c)); - if (isScrollBar && Color.WHITE.equals(c)) + if (isScrollBar&& (Color.WHITE.equals(c) || c.getRGB() == (0xFFEEEEEE & -1))) DOMNode.setStyles(sliderHandle, "background", "#ccc"); } diff --git a/sources/net.sf.j2s.java.core/src/swingjs/xml/JSJAXBClass.java b/sources/net.sf.j2s.java.core/src/swingjs/xml/JSJAXBClass.java index 42c90368a..d48211be1 100644 --- a/sources/net.sf.j2s.java.core/src/swingjs/xml/JSJAXBClass.java +++ b/sources/net.sf.j2s.java.core/src/swingjs/xml/JSJAXBClass.java @@ -145,7 +145,7 @@ private static void getDefaultNamespaceFromPackageInfo(Class javaClass) { null); if (clazz == null) break; - Object[][][] jsdata = (/** @j2sNative clazz.__ANN__ || */ + Object[][][] jsdata = (/** @j2sNative clazz.$getAnn$ && clazz.$getAnn$() || */ null); String className = (/** @j2sNative clazz.__CLASS$NAME__ || clazz.__CLASS_NAME__ || */ null); @@ -282,8 +282,10 @@ static boolean hasAnnotations(Object value) { @SuppressWarnings("unused") Class cl = value.getClass(); return (/** - * @j2sNative (value.$clazz$ ? !!value.$clazz$.__ANN__ : cl.$clazz$ ? - * !!cl.$clazz$.__ANN__ : 0) || + * @j2sNative value.$clazz$ + * && value.$clazz$.$getAnn$ + * && cl.$clazz$ + * && !!cl.$clazz$.$getAnn$ || */ false); } catch (Throwable t) { diff --git a/sources/net.sf.j2s.java.core/src/test/Test_.java b/sources/net.sf.j2s.java.core/src/test/Test_.java index ffb8d47cb..089135eb9 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_.java @@ -5,14 +5,19 @@ class Testx { // It will get its own .js file. } -class Test_ { +public class Test_ { - static boolean j2sHeadless = true; + static public boolean j2sHeadless = true; private static int i_ = 0; private int t = 0; + public void test123(int a, int b, int c) { + + } + public int test_int = 3; + public int showt() { if (true && (/**@j2sNative 1? test : */false)) { diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Annotation.java b/sources/net.sf.j2s.java.core/src/test/Test_Annotation.java new file mode 100644 index 000000000..2a299035a --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_Annotation.java @@ -0,0 +1,30 @@ +package test; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@interface Test_Annotation { + + @interface Test_Annotation1 { + String inner() default "inner"; + + @interface Test_Annotation2 { + String inner() default "inner2"; + } + + } + + Class cl() default Test_Annotation.class; + String name() default "test_annotate"; + String type() default "type"; + + @Test_Parameter + int itype() default -1; + + int[] iitype() default {1, 2, 3}; + boolean btest() default false; + boolean btest2() default true; + int itype2() default 1; + Test_Annotation1 ref() default @Test_Annotation1; +} \ No newline at end of file diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Annotation2.java b/sources/net.sf.j2s.java.core/src/test/Test_Annotation2.java new file mode 100644 index 000000000..f872453f6 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_Annotation2.java @@ -0,0 +1,7 @@ +package test; + +@interface Test_Annotation2 { + + String name() default "test_annotate"; + String type(); +} \ No newline at end of file diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Annotation_Impl.java b/sources/net.sf.j2s.java.core/src/test/Test_Annotation_Impl.java new file mode 100644 index 000000000..e2d02c92e --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_Annotation_Impl.java @@ -0,0 +1,136 @@ +package test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +@Test_Annotation(cl=Test_.class, btest = true, itype = 2, type = "im'pl", iitype = {4,5,6}) +@Test_Annotation.Test_Annotation1 +class Test_Annotation_Impl extends Test_ { + + static String stest1 = "stest1"; + static int itest1 = 3; + static int itest2; + static float f = Float.NaN; + static double d = Double.NaN; + + @Test_Parameter + public Test_Annotation_Impl() { + + } + + @Test_Parameter + public Test_Annotation_Impl(String s) { + + } + + static int ok; + static { + /** + * @j2sNative + * // alert("OK"); + */ + { + ok = 3; + } + } + + @Test_Parameter + String test1 = "test1"; + + @Test_Parameter + List>[] test2; + + @Test_Item + @Test_Parameter + int test3 = 3; + + @Test_Annotation(itype=4) + int itype = 5; + + { + ok = 5; + itype = 6; + } + + Test_ test4 = null; + Test_ test5; + + @Test_Parameter + @Test_Annotation(itype=6) + public String name() { + Object a = new String[4][]; + Object x = String.class; + Object y = String[].class; + return "class_impl"; + }; + + public static void main(String[] args) { + System.out.println(Test_Annotation_Impl.class.isAssignableFrom(Test_.class));//false + System.out.println(Test_.class.isAssignableFrom(Test_Annotation_Impl.class));//true + System.out.println(Test_.class.isAssignableFrom(Test_.class));//true + Test_Annotation_Impl impl = new Test_Annotation_Impl(); + try { + + Test_Annotation a = impl.getClass().getAnnotation(Test_Annotation.class); + System.out.println(a.annotationType().getName()); + processAnnotation(a, "class"); + + Class aclass = Test_Annotation.class; + Field[] fields = impl.getClass().getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + fields[i].setAccessible(true); +// System.out.println(fields[i]); + if (fields[i].isAnnotationPresent(aclass)) { + assert(fields[i].getName().equals("itype")); + processAnnotation(fields[i].getAnnotation(aclass), fields[i].getName()); + } + } + + Method[] methods = impl.getClass().getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + methods[i].setAccessible(true); + if (methods[i].isAnnotationPresent(aclass)) { + //minor issue that Method.getName() returns the fully qualified + // JavaScript method name, not the unqualified Java method name. + // OK? Good? Preferred? + +// assert(fields[i].getName().equals("name")); + processAnnotation(methods[i].getAnnotation(aclass), methods[i].getName() + "()"); + } + } + + System.out.println("Test_Annotation OK"); + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + private static void processAnnotation(Annotation a, String name) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + Method[] ma = a.getClass().getMethods(); + System.out.println("-------"); + for (int j = 0; j < ma.length; j++) { + if (ma[j].getDeclaringClass() != a.getClass()) + continue; + System.out.print(name + "." + ma[j].getName() + " "); + if (ma[j].getParameterTypes().length == 0 && !(ma[j].getReturnType().getName().equals("void"))) { + ma[j].setAccessible(true); + Object val = ma[j].invoke(a, new Object[0]); + if (val.getClass().isArray()) + val = Arrays.toString((int[]) val); + System.out.println( + "= " + val + " as " + ma[j].getReturnType().getName() + "(" + ma[j].getDefaultValue() + ")"); + } else { + System.out.println("??"); + } + } + + } +} \ No newline at end of file diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Array.java b/sources/net.sf.j2s.java.core/src/test/Test_Array.java index 4f31c2faf..e34cad5d6 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Array.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Array.java @@ -5,6 +5,7 @@ class Test_Array extends Test_ { static int[] i0 = new int[0]; + static Object ono = new Class[0]; static Object c33def2b = new int[][] { { 0, 1, 2 }, { 3, 4, 5 }, { 3, 4, 5 }, { 3, 4, 5 } }; diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Byte.java b/sources/net.sf.j2s.java.core/src/test/Test_Byte.java index e1059c895..d4a4ffcea 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Byte.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Byte.java @@ -1,5 +1,9 @@ package test; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + class Test_Byte extends Test_ { private static byte v=127; @@ -8,67 +12,74 @@ public static byte test1() { return v++; } - public static void main(String[] args) { + public static void main(String[] args) { + + byte[] ab = new byte[] { 0, 10, 0, 11, 0, 12, 1, 0, 2, 0, 3, 0 }; + + final ByteBuffer bb = ByteBuffer.wrap(ab); + short[] ash = new short[10]; - + bb.asShortBuffer().get(ash, 0, 5); + String st = Arrays.toString(ash); + assert(st.equals("[10, 11, 12, 256, 512, 0, 0, 0, 0, 0]")); + System.out.println(st); byte s = v++; System.out.println(s + " " + v); - assert((s + " " + v).equals("127 -128")); - + assert ((s + " " + v).equals("127 -128")); + char co = '\220'; assert (co == 144); v = 127; byte byte0 = 20; int i10 = 228; - int l1 = ((i10 * 100) / 4805) * (100 / byte0); - int i12 = 100 / byte0; - byte b2 = test1(); - - assert((b2 + " " + v).equals("127 -128")); - + int l1 = ((i10 * 100) / 4805) * (100 / byte0); + int i12 = 100 / byte0; + byte b2 = test1(); + + assert ((b2 + " " + v).equals("127 -128")); + // implied integer division can be done with int /= double d = 3; int di = 'c'; d /= 'c'; System.out.println(d); - assert(d == 3.0/99); - + assert (d == 3.0 / 99); + int i3 = 3; - System.out.println(i3/5); - + System.out.println(i3 / 5); byte b1 = 30; - + int ib = i3 | b1; - - assert(ib == 31); - + + assert (ib == 31); + // integer division must be turned back into an integer in JavaScript di /= 'c'; System.out.println(1); - assert(di == 1); + assert (di == 1); di = 100; - System.out.println(d/7/3); - assert(di / 7 / 3 == 4); + System.out.println(d / 7 / 3); + assert (di / 7 / 3 == 4); // JavaScript byte arrays require no wrapping - - byte[] ab = new byte[10]; + + ab = new byte[10]; ab[1] += 120; ab[2] = 120; ab[3] = 120; - ab[0] = (byte) ((ab[1] + ab[2] + ab[3])/3); // plus and div are not byte operators. + ab[0] = (byte) ((ab[1] + ab[2] + ab[3]) / 3); // plus and div are not byte operators. System.out.println(ab[0]); - assert(ab[0] == 120); - - - // all sorts of conversions. Note that short or byte operations other than bit shifts - // are really handled as int operations + assert (ab[0] == 120); + + // all sorts of conversions. Note that short or byte operations other than bit + // shifts + // are really handled as int operations int i255 = 0xFF; byte b = (byte) i255; - + b = 127; b += 127; b++; @@ -90,163 +101,156 @@ public static void main(String[] args) { assert (i == -1); b = (byte) c; b = (byte) 2; - int i2 = ((byte)4000 / b / b) * 30; + int i2 = ((byte) 4000 / b / b) * 30; System.out.println(i2); - assert(i2 == -720); - b/=2; + assert (i2 == -720); + b /= 2; b /= 0.01; System.out.println(b); assert (b == 100); - + // byte array pointer increment test - with character encoding - byte[] bok = new byte[] {'a', 2, 3, 4, 5}; + byte[] bok = new byte[] { 'a', 2, 3, 4, 5 }; b = -128; int pt = 0; bok[bok[++pt]] += 0; bok[bok[2]++] += 0; - - pt = 0; + + pt = 0; bok[pt++] |= '\2'; bok[pt++] = --b; - assert(pt == 2 && bok[0] == 'c' && bok[1] == 127); - bok[pt++] += b++; - assert(pt == 3 && bok[2] == -125 && b == -128); + assert (pt == 2 && bok[0] == 'c' && bok[1] == 127); bok[pt++] += b++; - assert(bok[3] == -124 && b == -127); - - + assert (pt == 3 && bok[2] == -125 && b == -128); + bok[pt++] += b++; + assert (bok[3] == -124 && b == -127); + // byte increment test - b = 127; + b = 127; i = b++; - assert(i == 127 && b==-128); - b = -128; + assert (i == 127 && b == -128); + b = -128; i = --b; - assert(i == 127 && b==127); + assert (i == 127 && b == 127); b = -128; i = b--; - assert(i == -128 && b==127); - + assert (i == -128 && b == 127); + b = 127; b++; - assert(b==-128); + assert (b == -128); --b; - assert(b==127); + assert (b == 127); ++b; - assert(b==-128); + assert (b == -128); b--; - assert(b==127); - - + assert (b == 127); + // byte shift tests - + b = 101; b <<= 2; - assert(b == -108); + assert (b == -108); b >>= 2; System.out.println("((byte)101 << 2) >>2 = " + b); - assert(b == -27); + assert (b == -27); b = 101; b = (byte) (b >>> 2); - assert(b == 25); + assert (b == 25); b = -101; b = (byte) (b >>> 2); System.out.println("((byte)-101 >>>2 = " + b); - assert(b == -26); + assert (b == -26); b = -1; b = (byte) (b >>> 2); System.out.println("((byte)-1 >>>2 = " + b); - assert(b == -1); + assert (b == -1); // int bit shifts i = -1; i = i >>> 2; System.out.println("(int)-1 >>> 2 = " + i); - assert(i == 1073741823); + assert (i == 1073741823); i = -1; i >>= 2; System.out.println("(int)-1 >>= 2 = " + i); - assert(i == -1); + assert (i == -1); i = -1; i >>>= 2; System.out.println("(int)-1 >>>= 2 = " + i); - assert(i == 1073741823); - - + assert (i == 1073741823); + // character increment test c = 'a'; c++; c--; ++c; --c; - assert(c == 'a'); + assert (c == 'a'); i = c++; - assert(i == 'a' && c == 'b'); + assert (i == 'a' && c == 'b'); i = ++c; - assert(i == 'c' && c == 'c'); - + assert (i == 'c' && c == 'c'); pt = 0; int p = 3; - int[] a = new int[] {1,2,3, 4, 5}; - a[2] |= 6; + int[] a = new int[] { 1, 2, 3, 4, 5 }; + a[2] |= 6; a[p++] |= 6; a[pt++] |= 2; - a[pt++] |= 4; - assert(pt == 2 && a[0] == 3 && a[1] == 6); - + a[pt++] |= 4; + assert (pt == 2 && a[0] == 3 && a[1] == 6); + // double array check - byte[][] aa = new byte[][] { {0, 0, 0}, {1,2,3}}; + byte[][] aa = new byte[][] { { 0, 0, 0 }, { 1, 2, 3 } }; pt = 0; aa[pt++][1] += 5; aa[pt++][1] += (int) (pt + 5.0); pt = 0; aa[1][pt++] += 5; aa[1][pt++] += 5; - assert(aa[0][1] == 5); - assert(aa[1][1] == 14); - - - byte[][][] aaa = new byte[][][] {{{0, 0, 0}, {1,2,3}, {4, 5, 6}}}; + assert (aa[0][1] == 5); + assert (aa[1][1] == 14); + + byte[][][] aaa = new byte[][][] { { { 0, 0, 0 }, { 1, 2, 3 }, { 4, 5, 6 } } }; pt = 0; p = 0; aaa[p++][pt++][1] += 5; aaa[0][pt++][1] += (int) (pt + 5.0); pt = 0; aaa[0][1][pt++] += 5; - assert(aaa[0][0][1] == 5); - assert(aaa[0][1][0] == 6); - assert(aaa[0][1][1] == 9); - - + assert (aaa[0][0][1] == 5); + assert (aaa[0][1][0] == 6); + assert (aaa[0][1][1] == 9); + // boolean[] test boolean[] aok = new boolean[200]; - - aok[100] |= true; - + + aok[100] |= true; + pt = 0; aok[pt++] |= true; - assert(pt == 1 && aok[0] == true); + assert (pt == 1 && aok[0] == true); // char[] test - + char[] cok = new char[3]; - pt = 0; + pt = 0; cok[0] = '\2'; - cok[pt++] |= 'a'; + cok[pt++] |= 'a'; // cok[$j$=pt++] = String.fromCharCode((cok[$j$]).$c()| 97); - assert(pt == 1 && cok[0] == 'c'); + assert (pt == 1 && cok[0] == 'c'); // quick test for double using += directly double line1[] = new double[20]; i2 = 1; - line1[i2+1] += Math.sin(3); - - + line1[i2 + 1] += Math.sin(3); + System.out.println("Test_Byte OK"); } diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Class.java b/sources/net.sf.j2s.java.core/src/test/Test_Class.java index 24950013e..3232e777a 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Class.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Class.java @@ -12,10 +12,21 @@ @SuppressWarnings("rawtypes") class Test_Class extends Test_Class2 { + Test_Class(byte[]...d) { + super(d); + System.out.println("Test_Class len = " + d.length); + } + + + + int x = 2000000000 + 2000000000; static int istatic = 5; static String sstatic = "test5"; + String same$ = null; + String same() {return null;} + static class Singleton { // reference to Test_Class.Singleton.instance // lazily initializes a new instance of Test_Class() @@ -136,7 +147,7 @@ public void mouseExited(MouseEvent e) { final String testfinal = "testFinal" + Double.valueOf(3); String pseudofinal = "testPseudo"; - + Hashtable t = new Hashtable() { public Object put(String key, Object value) { super.put(key, value); @@ -225,14 +236,38 @@ Class B() { static class C extends Test_Class { + C() { + super(); + } + + C(byte[]...d) { + super(d); + System.out.println("C len = " + d.length); + } + } Class C() { return C.class; } + + + public static void main(String[] args) { + + new C(new byte[0], new byte[100], new byte[1000]); + + new C(new byte[3][5]); + + try { + new C((byte[][])null); + } catch (Throwable t) { + System.out.println("Right!"); + } + + Class type = Object.class; assert(type instanceof Class); diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Class2.java b/sources/net.sf.j2s.java.core/src/test/Test_Class2.java index 7433f2e12..4f03af772 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Class2.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Class2.java @@ -94,6 +94,10 @@ public void mouseExited(MouseEvent e) { }; } + public Test_Class2(byte[]... d) { + System.out.println("Test_Class2 len = " + d.length); + } + public static void main(String[] args) { new Test_Class2(); new Test_Class2(); diff --git a/sources/net.sf.j2s.java.core/src/test/Test_ClassBase.java b/sources/net.sf.j2s.java.core/src/test/Test_ClassBase.java new file mode 100644 index 000000000..999b09c0c --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_ClassBase.java @@ -0,0 +1,67 @@ +package test; + +@SuppressWarnings("rawtypes") +class Test_ClassBase extends Test_ { + + public static class PTest extends PTest0 { + + @Override + public Integer add(String s, Integer n) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Integer remove(String s, Integer n) { + // TODO Auto-generated method stub + return null; + } + + } + + public static abstract class PTest0 implements PTestI { + + @Override + public N remove(String s, N n) { + // TODO Auto-generated method stub + return add(s, n); + } + + + } + + public interface PTestI { + public P add(T t, P p); + public P remove(T t, P p); + + } + + static class Parser extends BaseParser{ + + @Override + void test() { + System.out.println("Parser.test"); + } + + } + + static abstract class BaseParser { + + void doTest() { + test(); + } + + void test() { + System.out.println("BaseParser.test"); + } + } + + public static void main(String[] args) { + + new Test_ClassBase.Parser().doTest(); + + new PTest(); + + } + +} \ No newline at end of file diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Enum.java b/sources/net.sf.j2s.java.core/src/test/Test_Enum.java index 10b0742fd..19c4939e1 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Enum.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Enum.java @@ -1,5 +1,8 @@ package test; +import java.util.EnumSet; +import java.util.Iterator; + import javax.swing.LayoutStyle.ComponentPlacement; @SuppressWarnings("unused") @@ -9,6 +12,13 @@ public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } + public enum Dates { + D1,D2,D3,D4,D5,D6,D7,D8,D9,D10, + D11,D12,D13,D14,D15,D16,D17,D18,D19,D20, + D21,D22,D23,D24,D25,D26,D27,D28,D29,D30, + D31,D32,D33,D34,D35,D36,D37,D38,D39,D40 + } + Day day; public Test_Enum(Day day) { @@ -38,10 +48,10 @@ public String tellItLikeItIs() { public static void main(String[] args) { Day[] a = new Day[0]; - Day[] d = Day.values(); - for (int i = 0; i < d.length; i++) - System.out.println(i + " day " + d[i]); - System.out.println(d.toString()); + Day[] dd = Day.values(); + for (int i = 0; i < dd.length; i++) + System.out.println(i + " day " + dd[i]); + System.out.println(dd.toString()); System.out.println(Enum.valueOf(Day.class, "MONDAY").getClass().getName()); assert(Enum.valueOf(Day.class, "MONDAY").ordinal() == 1); @@ -56,6 +66,37 @@ public static void main(String[] args) { Test_Enum seventhDay = new Test_Enum(Day.SUNDAY); assert(seventhDay.tellItLikeItIs() == "Weekends are best."); Planet.main(new String[] { "155" }); + + EnumSet e = EnumSet.of(Day.MONDAY); + e.add(Day.WEDNESDAY); + e.add(Day.FRIDAY); + + Iterator it = e.iterator(); + + String mwf = ""; + while (it.hasNext()) { + mwf += it.next(); + } + + System.out.println(mwf); + assert(mwf.equals("MONDAYWEDNESDAYFRIDAY")); + + + EnumSet d = EnumSet.noneOf(Dates.class); + for (int i = 1; i < 40; i += 3) + d.add(Dates.valueOf("D" + i)); + + Iterator dt = d.iterator(); + + String d123 = ""; + while (dt.hasNext()) { + d123 += dt.next(); + } + + System.out.println(d123); + assert(d123.equals("D1D4D7D10D13D16D19D22D25D28D31D34D37")); + + System.out.println("Test_Enum OK"); } diff --git a/sources/net.sf.j2s.java.core/src/test/Test_File.java b/sources/net.sf.j2s.java.core/src/test/Test_File.java new file mode 100644 index 000000000..8fdfef1a1 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_File.java @@ -0,0 +1,19 @@ +package test; + +import java.io.File; +import java.io.IOException; + +public class Test_File extends Test_ { + + public static void main(String[] args) { + File f = new File("c:/temp/out.txt"); + System.out.println(f.getName()); + File p = f.getParentFile(); + System.out.println(f); + System.out.println(p); + p = new File("c:/temp"); + System.out.println(p.getAbsolutePath()); + System.out.println(p.isDirectory()); + System.out.println(p.exists()); + } +} \ No newline at end of file diff --git a/sources/net.sf.j2s.java.core/src/test/Test_GenericEABIXY2.java b/sources/net.sf.j2s.java.core/src/test/Test_GenericEABIXY2.java index 88b64dfa1..6085740b1 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_GenericEABIXY2.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_GenericEABIXY2.java @@ -1,58 +1,102 @@ package test; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** - * This is the troubling class. We have a method that appears not to have a generic-replaced method, - * because its class def has no indication of it. But actually it does, and we have to alias it in this - * class to all its precursor qualified names. - * + * This is the troubling class. We have a method that appears not to have a + * generic-replaced method, because its class def has no indication of it. But + * actually it does, and we have to alias it in this class to all its precursor + * qualified names. + * * @author RM * */ class Test_GenericEABIXY2 extends Test_GenericEABIXY { - + public class MyClass { // <- Type declaration + + class MyClassInner { + + } + + private T item; // <- Type usage + + T x; + + @Deprecated + public K getSubitem() { + // ^ ^ + // declaration usage + x = this.asList(); // <- Type Usage not a type declaration + return (K) x; + } + + private K asList() { + return null; + } + + } + public static void main(String[] args) { + + Object tgk = new Test_Generic_K() { + + @Override + int compareTo(Integer a) { + // TODO Auto-generated method stub + return 0; + } + + @Override + Integer get(Integer a) { + // TODO Auto-generated method stub + return null; + } + + }; + + Object myclassinner = new Test_GenericEABIXY2().new MyClass().new MyClassInner() { + + }; + Test_GenericEK c = new Test_GenericEK(); c.check("testa"); c.checkb("testc"); - + Test_GenericEABIXY tc = new Test_GenericEABIXY(); - assert (tc.compareTo(null, null) == 0); + assert (tc.compareTo(null, null) == 0); tc.test(null, null); Test_GenericIMV_AB tcc = new Test_GenericIMV_AB(); tcc.test(null, null); - Test_GenericEABIXY2 tc2 = new Test_GenericEABIXY2(); - int ret = tc2.compareTo("test", Integer.valueOf(1)); + int ret = tc2.compareTo("test", Integer.valueOf(1)); assert (ret == 1); - ret = tc2.compareTo("test", (Number) Integer.valueOf(0)); - assert(ret == 0); - - System.out.println("Test_GenericC OK"); - } + ret = tc2.compareTo("test", (Number) Integer.valueOf(0)); + assert (ret == 0); + System.out.println("Test_GenericC OK"); + } /** - * note that this has to be the EXACT signature as listed above, and it has to match. + * note that this has to be the EXACT signature as listed above, and it has to + * match. * */ public int compareTo(String s, Integer i) { System.out.println("Test_Generic2#compareTo(String Integer)" + s + "," + i); - return 1; - } + return 1; + } - public int show(String s, Integer i) { System.out.println("Test_Generic2#show(String Integer)" + s + "," + i); return 1; } - - - // visit Ltest/Test_GenericEABIMV2; // >> Ltest/Test_GenericEABIMV2;.compareTo(Ljava/lang/String;Ljava/lang/Integer;)I // overrides Ltest/Test_GenericEABIMV;.compareTo(Ljava/lang/String;Ljava/lang/Integer;)I @@ -62,4 +106,3 @@ public int show(String s, Integer i) { // overrides Ltest/Test_Generic_ABIMV_AB;.compareTo(Ljava/lang/String;Ljava/lang/Integer;)I } - diff --git a/sources/net.sf.j2s.java.core/src/test/Test_GenericEK.java b/sources/net.sf.j2s.java.core/src/test/Test_GenericEK.java index 201f32b7c..89eee2301 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_GenericEK.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_GenericEK.java @@ -11,6 +11,12 @@ public boolean checkb(String a) { public int compareTo(String s) { return s.compareTo("testb"); } + + @Override + String get(String a) { + // TODO Auto-generated method stub + return null; + } //we need to be able to equate $p.compareTo$TK = $p.compareto$S diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Generic_Java8.java b/sources/net.sf.j2s.java.core/src/test/Test_Generic_Java8.java index 559d52b4d..fb3f5da55 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Generic_Java8.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Generic_Java8.java @@ -1,5 +1,8 @@ package test; +import java.util.List; +import java.util.ArrayList; + import javax.swing.DefaultListModel; import javax.swing.JComboBox; import javax.swing.JComponent; @@ -9,6 +12,12 @@ public class Test_Generic_Java8 extends Test_ implements ListCellRenderer { + + public static List asList(T... a) { + return (List) new ArrayList(); + } + + /** * Demonstrates the issue with Java8 classes being generic while Java6 * version is not. As long as the transpiler is working in a Java8 @@ -21,6 +30,8 @@ public class Test_Generic_Java8 extends Test_ implements ListCellRenderer { */ public static void main(String[] args) { + System.out.println(Test_Generic_Java8.asList("testing")); + Test_Generic_Java8 test = new Test_Generic_Java8(); // calls getListCellRendererComponent$javax_swing_JList$O$I$Z$Z test.getListCellRendererComponent(null, null, 0, false, false); diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Generic_K.java b/sources/net.sf.j2s.java.core/src/test/Test_Generic_K.java index 4181e39ca..365208a6f 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Generic_K.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Generic_K.java @@ -4,14 +4,24 @@ abstract class Test_Generic_K { // we can explore the as binding.getTypeParameters() + K value; abstract int compareTo(K a); // will be String + abstract K get(K a); // will be String + + @Test_Parameter int check(K a) { + value = a; // this one is referencing K so it reads compareTo$TK(a) but must call compareTo$S(a). return compareTo(a); } + + @Test_Parameter + K checkK(K a) { + return a; + } } diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Ints.java b/sources/net.sf.j2s.java.core/src/test/Test_Ints.java index 1bfb7af7c..d88b7a89b 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Ints.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Ints.java @@ -26,11 +26,75 @@ Map build() { map.putIfAbsent("test", 0x00); return map; } - + + static long[] uia = /** @j2sNative new Uint32Array(1) || */null; + + private static long uint(int i) { + /** + * @j2sNative + * + * return uia[0] = i, uia[0]; + */ + return 0xFFFFFFFFL & i; + } + + + private static int getComponent(final int pixel) { + final long v = uint(pixel); + System.out.println((Math.pow(2, 32) - 1)); + System.out.println(0xFFFFFFFFL); + double f = v / 0xFFFFFFFFL; + f *= 255; + return (int) f; + } private final static int USER = -13; private final static int ALT = 16; public static void main(String[] args) { + + System.out.println(0xffffffffffffffffL); + + System.out.println(Integer.MAX_VALUE); + System.out.println((int) Math.pow(2, 32) - 1); + System.out.println((int) (Math.pow(2, 32) - 1)); + System.out.println((long) (Math.pow(2, 32) - 1)); + + System.out.println(getComponent(0)); + System.out.println(getComponent(-1)); + + int ix = -1; + // does not work: + System.out.println(ix & 0xFFFFFFFFL); + + + System.out.println((0xFFFFFFFFL + & ((-1<<24) | ((0xFF & -1)<<16) + | ((0xFF & -1)<<8) | (0xFF & -1)))); + + long[] uint = /** @j2sNative new Uint32Array(1) || */new long[0]; + + System.out.println(/** @j2sNative 1?(uint[0] = ix, uint[0]) : */ix & 0xFFFFFFFFL); + + long lx = 0x300000005L; + System.out.println(lx % 0x100000000L); + + System.out.println(lx & 0xFFFFFFFF); + System.out.println(lx & -1); + System.out.println(lx & 0xFFFFFFFFL); + + System.out.println((int) lx); + System.out.println((int) (lx & 0xFFFFFFFF)); + System.out.println((int) (lx & -1)); + System.out.println((int) (lx & 0xFFFFFFFFL)); + + + System.out.println(Math.pow(2, 32)); + System.out.println((int) Math.pow(2, 32)); + System.out.println((int) Math.pow(2, 32) - 1); + System.out.println(1L << 32); + + + int i0b = new Integer(0b010101).intValue(); assert(i0b == 21); new Test_Ints(0b11,0b01); diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Item.java b/sources/net.sf.j2s.java.core/src/test/Test_Item.java new file mode 100644 index 000000000..42c9c9d97 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_Item.java @@ -0,0 +1,5 @@ +package test; + +public @interface Test_Item { + +} diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Native.java b/sources/net.sf.j2s.java.core/src/test/Test_Native.java index 9e5c5fb38..fa04641dd 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Native.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Native.java @@ -24,6 +24,19 @@ class Test_Native extends Test_ { + /**@j2sAlias *,P,* */ + @Override + public void test123(int a, int b, int c) { + + } + + + /** @j2sAlias *,P,* */ + void checkP(int i, Object obj, int j) { + + } + + /** * @j2sNative * diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Parameter.java b/sources/net.sf.j2s.java.core/src/test/Test_Parameter.java new file mode 100644 index 000000000..5621fbd82 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_Parameter.java @@ -0,0 +1,4 @@ +package test; + +public @interface Test_Parameter { +} diff --git a/sources/net.sf.j2s.java.core/src/test/Test_RAF.java b/sources/net.sf.j2s.java.core/src/test/Test_RAF.java new file mode 100644 index 000000000..2f85ef7df --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_RAF.java @@ -0,0 +1,65 @@ +package test; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javajs.util.Rdr; +import javajs.util.ZipTools; + +/** + * Test for RandomAccessFile + * @author hansonr + * + */ +public class Test_RAF extends Test_ { + + public static void main(String[] args) { + try { + File t = File.createTempFile("test-raf", ".txt"); + System.out.println(t.getAbsolutePath()); + RandomAccessFile raf = new RandomAccessFile(t, "rw"); + assert(raf.length() == 0); + raf.seek(844000); + assert(raf.read() == -1); + raf.close(); + raf = new RandomAccessFile(t, "rw"); + // UTF string + raf.seek(8); + raf.writeUTF("testing"); + assert(raf.length() == 17); +// byte[] b = "testing".getBytes(); +// raf.writeShort(b.length); +// raf.write(b); + raf.close(); + raf = new RandomAccessFile(t, "rw"); + assert(raf.length() == 17); + System.out.println("tested for temp file length 0, writing UTF 'testing' at byte 8, length now 17"); + raf.seek(8); + String val = raf.readUTF(); + raf.close(); + assert(val.equals("testing")); + System.out.println("tested for UTF value being '" + val + "'"); + t.delete(); + boolean deleted = false; + try { + raf = new RandomAccessFile(t, "r"); + } catch (Exception e) { + deleted = true; + assert(e instanceof FileNotFoundException); + } + assert(deleted); + System.out.println("tested for temp file deleted"); + System.out.println("Test_RAF OK"); + } catch (Exception e) { + e.printStackTrace(); + } + + } +} \ No newline at end of file diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Reflect.java b/sources/net.sf.j2s.java.core/src/test/Test_Reflect.java index 6c46cddb5..2514a8d1b 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Reflect.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Reflect.java @@ -2,11 +2,19 @@ import java.awt.Button; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; class Test_Reflect extends Test_ { - static long ltime = System.currentTimeMillis(); + public static long ltime = System.currentTimeMillis(); public String s = "field s"; + public int[] testing; + public Test_ test; public void test(char i, String s) { System.out.println("testchar " + i);assert(s.equals("char")); @@ -54,63 +62,109 @@ public void test(String s, float[][] aaf) { } /* -test.Test_Reflect.getMethod$S$ClassA ("test", [Character.TYPE, String]).invoke$O$OA (tr, [new Character ('K').charCodeAt (0), "char"]); -test.Test_Reflect.getMethod$S$ClassA ("test", [Character.TYPE, String]).invoke$O$OA (tr, [new Character ('K').charCodeAt (0), "char"]); -test.Test_Reflect.getMethod$S$ClassA ("test", [Integer, String]).invoke$O$OA (tr, [Integer.$valueOf (10), "Integer"]); -test.Test_Reflect.getMethod$S$ClassA ("test", [Integer.TYPE, String]).invoke$O$OA (tr, [new Integer (10), "int"]); -test.Test_Reflect.getMethod$S$ClassA ("test", [Integer.TYPE, String]).invoke$O$OA (tr, [Short.$valueOf (10), "int"]); -test.Test_Reflect.getMethod$S$ClassA ("test", [Integer.TYPE, String]).invoke$O$OA (tr, [new Short (10), "int"]); -test.Test_Reflect.getMethod$S$ClassA ("test", [Float, String]).invoke$O$OA (tr, [Float.$valueOf (10), "Float"]); -test.Test_Reflect.getMethod$S$ClassA ("test", [Float.TYPE, String]).invoke$O$OA (tr, [new Integer (10), "float"]); -test.Test_Reflect.getMethod$S$ClassA ("test", [String, Clazz.arrayType('int[]')]).invoke$O$OA (tr, ["int[]", Clazz.newIntArray (-1, [10])]); -test.Test_Reflect.getMethod$S$ClassA ("test", [String, Clazz.arrayType('float[]')]).invoke$O$OA (tr, ["float[]", Clazz.newFloatArray (-1, [10])]); -test.Test_Reflect.getMethod$S$ClassA ("test", [String, Clazz.arrayType('test.Test_Reflect[]')]).invoke$O$OA (tr, ["test.Test_Reflect[]", Clazz.newArray (-1, [tr])]); -test.Test_Reflect.getMethod$S$ClassA ("test", [String, Clazz.arrayType('java.lang.String[]')]).invoke$O$OA (tr, ["String[]", Clazz.newArray (-1, ["10"])]); -test.Test_Reflect.getMethod$S$ClassA ("test", [String, Clazz.arrayType('java.lang.String[][]')]).invoke$O$OA (tr, ["String[][]", Clazz.newArray (-1, [ Clazz.newArray (-1, ["10"]), null])]); -test.Test_Reflect.getMethod$S$ClassA ("test", [String, Clazz.arrayType('float[][]')]).invoke$O$OA (tr, ["float[][]", Clazz.newArray (-1, [ Clazz.newFloatArray (-1, [10]), null])]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [Character.TYPE, + * String]).invoke$O$OA (tr, [new Character ('K').charCodeAt (0), "char"]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [Character.TYPE, + * String]).invoke$O$OA (tr, [new Character ('K').charCodeAt (0), "char"]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [Integer, String]).invoke$O$OA + * (tr, [Integer.$valueOf (10), "Integer"]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [Integer.TYPE, + * String]).invoke$O$OA (tr, [new Integer (10), "int"]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [Integer.TYPE, + * String]).invoke$O$OA (tr, [Short.$valueOf (10), "int"]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [Integer.TYPE, + * String]).invoke$O$OA (tr, [new Short (10), "int"]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [Float, String]).invoke$O$OA + * (tr, [Float.$valueOf (10), "Float"]); test.Test_Reflect.getMethod$S$ClassA + * ("test", [Float.TYPE, String]).invoke$O$OA (tr, [new Integer (10), "float"]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [String, + * Clazz.arrayType('int[]')]).invoke$O$OA (tr, ["int[]", Clazz.newIntArray (-1, + * [10])]); test.Test_Reflect.getMethod$S$ClassA ("test", [String, + * Clazz.arrayType('float[]')]).invoke$O$OA (tr, ["float[]", Clazz.newFloatArray + * (-1, [10])]); test.Test_Reflect.getMethod$S$ClassA ("test", [String, + * Clazz.arrayType('test.Test_Reflect[]')]).invoke$O$OA (tr, + * ["test.Test_Reflect[]", Clazz.newArray (-1, [tr])]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [String, + * Clazz.arrayType('java.lang.String[]')]).invoke$O$OA (tr, ["String[]", + * Clazz.newArray (-1, ["10"])]); test.Test_Reflect.getMethod$S$ClassA ("test", + * [String, Clazz.arrayType('java.lang.String[][]')]).invoke$O$OA (tr, + * ["String[][]", Clazz.newArray (-1, [ Clazz.newArray (-1, ["10"]), null])]); + * test.Test_Reflect.getMethod$S$ClassA ("test", [String, + * Clazz.arrayType('float[][]')]).invoke$O$OA (tr, ["float[][]", Clazz.newArray + * (-1, [ Clazz.newFloatArray (-1, [10]), null])]); */ public static void main(String[] args) { + + List list = new ArrayList<>(); + Map.Entryentry = new Map.Entry(){ + + @Override + public String getKey() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Integer getValue() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Integer setValue(Integer value) { + // TODO Auto-generated method stub + return null; + } + + }; + Method[] m = Map.Entry.class.getDeclaredMethods(); + for (int i = 0; i < m.length; i++) + System.out.println(m[i].getName() + "\t" + m[i].getReturnType().getName()); + + - new Test_Reflect().test("int[]", new int[] {1,2,3}); + new Test_Reflect().test("int[]", new int[] { 1, 2, 3 }); String name = ""; try { - Test_Path tp = (Test_Path) Class.forName(name = "Test_Path", true, Test_Reflect.class.getClassLoader()).newInstance(); - assert(tp != null); + Test_Path tp = (Test_Path) Class.forName(name = "test.Test_Path", true, Test_Reflect.class.getClassLoader()) + .newInstance(); + assert (tp != null); System.out.println("class loaded: " + tp.getClass().getName()); - Button b = (Button) Class.forName(name = "java.awt.Button", true, Test_Reflect.class.getClassLoader()).newInstance(); - assert(b != null); + Button b = (Button) Class.forName(name = "java.awt.Button", true, Test_Reflect.class.getClassLoader()) + .newInstance(); + assert (b != null); System.out.println("class loaded: " + b.getClass().getName()); } catch (Throwable t) { System.out.println("problems loading " + name); - assert(false); + t.printStackTrace(); + assert (false); } - + Test_Reflect tr = new Test_Reflect(); - + new Test_Char(); - assert(tr instanceof Test_); - assert(tr instanceof Test_); - assert(!(((Test_)tr) instanceof Test_Char)); + assert (tr instanceof Test_); + assert (tr instanceof Test_); + assert (!(((Test_) tr) instanceof Test_Char)); long l = System.currentTimeMillis(); - System.out.println(l - ltime); + System.out.println("time/ms to here " + (l - ltime)); for (int i = 0; i < 10000; i++) { - assert(!(((Test_)tr) instanceof Test_Char)); + assert (!(((Test_) tr) instanceof Test_Char)); } - System.out.println(l - System.currentTimeMillis()); - - - // Field is not implemented + System.out.println("time/ms for 10000 instanceof " + (System.currentTimeMillis() - l)); + + // Field is not implemented // Field f = Test_Reflect.class.getDeclaredField("s"); // System.out.println(f.getDeclaringClass()); - + try { - Test_Reflect.class.getMethod("test", char.class, String.class).invoke(tr, (char)75, "char"); - Test_Reflect.class.getMethod("test", Character.TYPE, String.class).invoke(tr, (char)75, "char"); + Test_Reflect.class.getMethod("test", char.class, String.class).invoke(tr, (char) 75, "char"); + Test_Reflect.class.getMethod("test", Character.TYPE, String.class).invoke(tr, (char) 75, "char"); Test_Reflect.class.getMethod("test", Integer.class, String.class).invoke(tr, Integer.valueOf(10), "Integer"); @@ -124,12 +178,13 @@ public static void main(String[] args) { int[][] ii = new int[2][2]; ii[0][0] = 10; float[][] ff = new float[2][]; - ff[0] = new float[] {10}; - String[][] ss = new String[][] {new String[] {"10"}, null}; - + ff[0] = new float[] { 10 }; + String[][] ss = new String[][] { new String[] { "10" }, null }; + Test_Reflect.class.getMethod("test", String.class, int[].class).invoke(tr, "int[]", ii[0]); Test_Reflect.class.getMethod("test", String.class, float[].class).invoke(tr, "float[]", ff[0]); - Test_Reflect.class.getMethod("test", String.class, test.Test_Reflect[].class).invoke(tr, "test.Test_Reflect[]", new test.Test_Reflect[] {tr}); + Test_Reflect.class.getMethod("test", String.class, test.Test_Reflect[].class).invoke(tr, + "test.Test_Reflect[]", new test.Test_Reflect[] { tr }); Test_Reflect.class.getMethod("test", String.class, String[].class).invoke(tr, "String[]", ss[0]); Test_Reflect.class.getMethod("test", String.class, String[][].class).invoke(tr, "String[][]", ss); @@ -138,24 +193,37 @@ public static void main(String[] args) { try { // intentional failure to invoke Test_Reflect.class.getMethod("test", String.class, int[][][][].class).invoke(tr, "int[][][][]", ii[0]); - assert(false); + assert (false); } catch (Exception e) { - assert(e instanceof NoSuchMethodException); + assert (e instanceof NoSuchMethodException); } } catch (Throwable e) { - assert(false); + assert (false); } Test_Reflect t = new Test_Reflect(); Class f = t.getClass(); - Field[] fields = f.getDeclaredFields(); - for (int i = 0; i < fields.length; i++) - try { + try { + Field[] fields = f.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) + System.out.println("declaredField " + i + ": " + fields[i].getName() + " " + fields[i].get(t) + " as " + fields[i].getType().getName()); + f.getField("s").set(t, "news"); + System.out.println("t.s=" + t.s); + assert (t.s.equals("news")); + fields = f.getFields(); + for (int i = 0; i < fields.length; i++) System.out.println("field " + i + ": " + fields[i].getName() + " " + fields[i].get(t)); - } catch (IllegalArgumentException | IllegalAccessException e) { - e.printStackTrace(); - } - - System.out.println("Test_Reflect OK"); + + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + System.out.println("Test_Reflect OK"); } } \ No newline at end of file diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Regex.java b/sources/net.sf.j2s.java.core/src/test/Test_Regex.java new file mode 100644 index 000000000..0a5f65027 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/Test_Regex.java @@ -0,0 +1,66 @@ +package test; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class Test_Regex extends Test_ { + + public static void main(String[] args) { + Map vars = new HashMap<>(); + vars.put("a2", Double.valueOf(2)); + vars.put("a3", Double.valueOf(3)); + assert(eval("y+a2*x+a3", vars).equals("y+(2.0)*x+(3.0)")); + assert(eval("y + a2*x + a3", vars).equals("y+(2.0)*x+(3.0)")); + assert(eval("y + -a2*x + a3", vars).equals("y+-(2.0)*x+(3.0)")); + String s = Arrays.toString(splitUnquoted("this; is \"a;test\"", ";")); + System.out.println(s); + assert(s.equals("[this, is \"a;test\"]")); + System.out.println("Test_Regex OK"); + } + + public static String[] splitUnquoted(final String s, final String separator) { + // See https://stackoverflow.com/a/1757107/1919049 + return s.split(Pattern.quote(separator) + + "(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)", -1); + } + + public static String eval(String s, Map vars) { + final StringBuilder sb = new StringBuilder(); + // NB: Split general equation on potential variables, including delimiters. + // For an explanation, see: http://stackoverflow.com/a/279337 + // BH But not all browsers can handle look-behind. + // So we allow for ending up with \\W\\w+ + s = s.replaceAll(" ",""); + String pat = /** @j2sNative "(?=\\W)" || */ "(?<=\\w)(?=\\W)|(?<=\\W)(?=\\w)"; + final String[] tokens = s.split(pat); + for (String token : tokens) { + if (token.matches("\\w+")) { + // abc + } else if (token.matches("\\W\\w+")) { + // +abc + sb.append(token.charAt(0)); + token = token.substring(1); + } else { + sb.append(token); + continue; + } + // token might be a variable; check the vars table + final Double value = vars.get(token); + if (value == null) { + sb.append(token); + } else { + // token *is* a variable; substitute the value! + sb.append("("); + sb.append(value); + sb.append(")"); + } + } + System.out.println(s + " = " + sb.toString()); + return sb.toString(); + } + + +} diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Resource.java b/sources/net.sf.j2s.java.core/src/test/Test_Resource.java index c7d12878e..cdcc234ae 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Resource.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Resource.java @@ -1,5 +1,6 @@ package test; +import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -12,6 +13,8 @@ static int getInt() { return 0; } + static boolean isClosed = false; + public static void main(String[] args) { Properties p = new Properties(); try { @@ -21,19 +24,40 @@ public static void main(String[] args) { String test = p.getProperty("test"); System.out.println(test); assert("OK".equals(test)); - System.out.println("Test_Resource OK"); } catch (Exception e) { System.out.println(e); assert(false); } - - - try(OutputStream os = new FileOutputStream("test.test")) { - - } catch (IOException e) { - e.printStackTrace(); + + try { + File f = File.createTempFile("test", ".txt"); + try(OutputStream os = new FileOutputStream(f) { + public void close() { + try { + System.out.println("closing OutputStream "); + isClosed = true; + super.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + }) { + os.write("test".getBytes()); + // this will close the output stream as a 4-byte file + } catch (IOException e) { + e.printStackTrace(); + } + System.out.println(f.getName() + " length=" + f.length()); + assert(f.length() == 4); + assert(isClosed); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); } + + System.out.println("Test_Resource OK"); } } \ No newline at end of file diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Vararg.java b/sources/net.sf.j2s.java.core/src/test/Test_Vararg.java index 12b6653f8..10b1429bf 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Vararg.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Vararg.java @@ -1,5 +1,8 @@ package test; +import java.util.Arrays; +import java.util.List; + @SuppressWarnings("unused") class Test_Vararg extends Test_ { @@ -20,7 +23,7 @@ private void testVar(String s, int i) { private void testVarI(String s, int... a) { System.out.println("testVar inta len=" + a.length + " " + s); - assert(s.equals("int[]")); + assert(s.equals("int[]") && (a.length < 2)); } @@ -38,6 +41,17 @@ private void testVarO(String s, Object[]... aa) { assert(s.equals("Object[]")); } + private void testVarN(String s, Number... aa) { + System.out.println("testVar Number len=" + aa.length + " " + s); + assert(s.equals("Number")); + } + + private void testVarD(String s, double... aa) { + System.out.println("testVar Double len=" + aa.length + " " + s); + assert(s.equals("Double")); + } + + public static void main(String[] args) { Test_Vararg t = new Test_Vararg(); @@ -45,15 +59,24 @@ public static void main(String[] args) { Test_Vararg t21 = new Test_Vararg(1, 1); Test_Vararg t1f = new Test_Vararg(2f); t21.testVar("int", 1); - t21.testVarI("int[]", 2, 1); + t21.testVarI("int[]", 2); + t21.testVarI("int[]", new int[] {1}); t21.testVarI("int[]"); t21.testVarII("int[][]", new int[] {1}); t21.testVarII("int[][]", new int[] {2}, new int[] {1}); t21.testVarII("int[][]"); t21.testVarO("Object[]", new Object[] {"1", "2", "3"}); t21.testVarO("Object[]", new Object[] {} , new Object[] {}); + + t21.testVarN("Number", new Integer(3)); + t21.testVarN("Number", new Integer[] {null, null, null}); + t21.testVarD("Double", 1.0,2,3); + + List> x = Arrays.>asList(Test_.class); + System.out.println("Test_Vararg OK"); + // try { // t21.testVar("int[]", null); // } catch (NullPointerException e) { diff --git a/sources/net.sf.j2s.java.core/src/test/Test_Zipout.java b/sources/net.sf.j2s.java.core/src/test/Test_Zipout.java index 9f497973c..844f9e7f9 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_Zipout.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_Zipout.java @@ -9,7 +9,10 @@ public class Test_Zipout extends Test_ { public static void main(String[] args) { try { - byte[] bytes = "this is a test".getBytes(); + String s = ""; + for (int i = 0; i < 4000; i++) + s += i + "\tthis is a test\n"; + byte[] bytes = s.getBytes(); OutputStream os = new FileOutputStream("C:/temp/testzip.zip"); ZipOutputStream zos = new ZipOutputStream(os); zos.putNextEntry(new ZipEntry("test1.txt")); diff --git a/sources/net.sf.j2s.java.core/src/test/Test_inta.java b/sources/net.sf.j2s.java.core/src/test/Test_inta.java index 0d9ac85b4..451cb8924 100644 --- a/sources/net.sf.j2s.java.core/src/test/Test_inta.java +++ b/sources/net.sf.j2s.java.core/src/test/Test_inta.java @@ -14,5 +14,4 @@ public String testInt(Number n) { return null; } - } diff --git a/sources/net.sf.j2s.java.core/src/test/test-raf.txt b/sources/net.sf.j2s.java.core/src/test/test-raf.txt new file mode 100644 index 000000000..163504823 --- /dev/null +++ b/sources/net.sf.j2s.java.core/src/test/test-raf.txt @@ -0,0 +1 @@ +this is a testing RandomAccessFile diff --git a/sources/net.sf.j2s.java.core/srcjs/js/j2sApplet.js b/sources/net.sf.j2s.java.core/srcjs/js/j2sApplet.js index 2c49af0dd..d5483a4b8 100644 --- a/sources/net.sf.j2s.java.core/srcjs/js/j2sApplet.js +++ b/sources/net.sf.j2s.java.core/srcjs/js/j2sApplet.js @@ -1209,7 +1209,8 @@ console.log("J2S._getRawDataFromServer " + J2S._serverUrl + " for " + query); return Clazz.loadClass("javajs.util.Base64").decodeBase64$S( s.substring(8)); } - // not UTF-8 + // return Clazz.array(new Int8Array(new TextEncoder().encode(s).buffer),Clazz.array(Byte.TYPE)); + // not UTF-8 - this is a Chrome encoding of the bytes as "string" var b = Clazz.array(Byte.TYPE, s.length); for (var i = s.length; --i >= 0;) b[i] = s.charCodeAt(i) & 0xFF; diff --git a/sources/net.sf.j2s.java.core/srcjs/js/j2sClazz.js b/sources/net.sf.j2s.java.core/srcjs/js/j2sClazz.js index f1383ddd3..70f7e2615 100644 --- a/sources/net.sf.j2s.java.core/srcjs/js/j2sClazz.js +++ b/sources/net.sf.j2s.java.core/srcjs/js/j2sClazz.js @@ -7,6 +7,10 @@ // Google closure compiler cannot handle Clazz.new or Clazz.super +// BH 2019.12.29 3.2.6 fixes Float.parseFloat$S("NaN") [and Double] +// BH 2019.12.23 3.2.6 update of System +// BH 2019.12.19 3.2.6 revision of $clinit$ +// BH 2019.12.16 3.2.5.v4 adds ClassLoader static methods for system resources (just j2s/...) // BH 2019.12.15 3.2.5.v4 Character.prototype.valueOf() missing // BH 2019.12.14 3.2.5.v3 Clazz._4Name initialization should be full static initialization @@ -63,7 +67,7 @@ Clazz.ClassFilesLoaded = []; Clazz.popup = Clazz.log = Clazz.error = window.alert; /* can be set by page JavaScript */ -Clazz.defaultAssertionStatus = true; +Clazz.defaultAssertionStatus = false; /* can be set by page JavaScript */ Clazz._assertFunction = null; @@ -113,7 +117,8 @@ var _array = function(baseClass, paramType, ndims, params, isClone) { return copyArrayProps(a, b); } if (arguments.length == 2 && baseClass.BYTES_PER_ELEMENT) { - // direct transfer of parameters from java.nio.ByteBuffer + // Clazz.array(rawInt8Array, int[] array) + // direct transfer of __* metadata (see java.nio.ByteBuffer) return copyArrayProps(paramType, baseClass); } var prim = Clazz._getParamCode(baseClass); @@ -346,10 +351,11 @@ Clazz.instanceOf = function (obj, clazz) { // unwrap java.lang.Class to JavaScript clazz using $clazz$ if (typeof clazz == "string") { clazz = Clazz._getDeclared(clazz); - } - + } if (obj == null || !clazz) return false; + if (obj == clazz) + return true; // check for object being a java.lang.Class and the other not if (obj.$clazz$ && !clazz.$clazz$) return (clazz == java.lang.Class); obj.$clazz$ && (obj = obj.$clazz$); @@ -386,10 +392,21 @@ Clazz.load = function(cName, from$clinit$) { if (from$clinit$ == 1) { // C$.$clinit$ call to finalize all dependencies var cl = cName; - cl.$clinit$ = 0; + cl.$clinit$ = 0-cl.$clinit$; + // -2 means v 3.2.6 + // -1 means v 3.2.5 + // NaN means original 3.2.4 function() {Clazz.load(C$, 1)}; var ld = cl.$load$; setSuperclass(cl, (ld && ld[0] ? Clazz.load(ld[0]) : null)); ld[1] && addInterface(cl, ld[1]); + switch (cl.$clinit$) { + case -1: + // done + break; + case -2: + initClass0(cl); + break; + } return; } if (from$clinit$ == 2) { @@ -421,6 +438,132 @@ Clazz.load = function(cName, from$clinit$) { return Clazz._4Name(cName, null, null, true); } +//create and $init0$ +var initClass0 = function(c) { + var fields = c.$fields$; + var objects = fields && fields[0]; + createDefaults(c, objects, false); + if (!fields) + return; + var statics = fields[1]; + if (statics && statics.length) + createDefaults(c, statics, true); +} + +//C$.$fields$=[ +//['I',['test3','itype'],'S',['test1'],'O',['test2','java.util.List[]','test4','test.Test_','+test5']], +//['D',['d'],'F',['f'],'I',['itest1','itest2'],'S',['stest1']] +//] +var createDefaults = function(c, data, isStatic) { + var a = getFields(c, data, true); + if (isStatic) { + for (var i = a.length; --i >= 0;) { + c[a[i][0]] = a[i][1]; + } + } else { + c.$init0$ = + //(function(cs, a) {return + function(){ + var cs = c.superclazz; + cs && cs.$init0$ && cs.$init0$.apply(this); + for (var i = a.length; --i >= 0;){ + this[a[i][0]] = a[i][1]; + } + }; +// })(c.superclazz, a); + } + +} + +Clazz._getFieldNames = function(c, isStatic) { + return (c.$fields$ ? getFields(c, c.$fields$[isStatic ? 1 : 0], 0) : []); +} + +Clazz._getFieldTypes = function(c, isStatic) { + return (c.$fields$ ? getFields(c, c.$fields$[isStatic ? 1 : 0], "types") : []); +} + +var fieldTypes = "Integer;Float;Double;Character;Long;Byte;" +/** + * Get correct default (0, '\0', null) or just + * return a list of names. + */ +var getFields = function(c, data, andDefaults) { + var a = []; + if (!data) + return a; + if (andDefaults == "types") { + for (var i = 0, n = data.length; i < n; i++) { + var type = data[i++]; + var anames = data[i]; + if (type != "O") { + for (var j = anames.length; --j >= 0;) + a.push(type); + continue; + } + type = "String"; + for (var j = 0, na = anames.length; j < na; j++) { + if (anames[j].indexOf("+") != 0) { + type = anames[++j]; + } + a.push(type); + } + continue; + } + return a; + } + if (andDefaults) { + for (var i = 0, n = data.length; i < n; i++) { + var type = data[i++]; + var anames = data[i]; + var defval; + switch (type) { + case 'S': + defval = null; + break; + case 'O': + for (var j = 0, na = anames.length; j < na; j++) { + var name = anames[j]; + if (name.indexOf("+") == 0) + name = name.substring(1); + else + j++; + a.push([name, null]); + } + continue; + case 'C': + defval = '\0'; + break; + default: + defval = 0; + break; + } + for (var j = 0, na = anames.length; j < na; j++) { + a.push([anames[j], defval]); + } + } + return a; + } + for (var i = 0, n = data.length; i < n; i++) { + var type = data[i++]; + var anames = data[i]; + if (type == 'O') { + for (var j = 0, na = anames.length; j < na; j++) { + var name = anames[j]; + if (name.indexOf("+") == 0) + name = name.substring(1); + else + j++; + a.push(name); + } + } else { + for (var j = 0, na = anames.length; j < na; j++) { + a.push(anames[j]); + } + } + } + return a; +} Clazz._newCount = 0; /** @@ -436,6 +579,16 @@ Clazz._newCount = 0; Clazz.new_ = function(c, args, cl) { if (!c) return new Clazz._O(); + var generics; + var a = arguments; + if (c === 1) { // new for 3.2.6 {K:"java.lang.String",...} + generics = arguments[1]; + a = []; + c = a[0] = arguments[2]; + args = a[1] = arguments[3]; + cl = a[2] = arguments[4]; + a = a.slice(0, arguments.length - 2); + } var haveArgs = !!args; args || (args = [[]]); @@ -458,7 +611,7 @@ Clazz.new_ = function(c, args, cl) { // or a static field or method call (which is handled // by the $I$(n) handler in the function initializer in // the newClass() call. - var obj = new (Function.prototype.bind.apply(cl, arguments)); + var obj = new (Function.prototype.bind.apply(cl, a)); if (args[2] != inheritArgs) { haveArgs && c.apply(obj, args); clInner && clInner.$init$.apply(obj); @@ -466,6 +619,9 @@ Clazz.new_ = function(c, args, cl) { _profileNew && addProfileNew(cl, window.performance.now() - t0); + if (generics) { + obj.$init$.generics = generics; + } return obj; } @@ -818,17 +974,21 @@ Clazz.super_ = function(cl, obj) { ///////////////////////////////////////////////////////////////////// var aas = "AAA"; -/** - * Create an array class placeholder for reflection - */ + +var arrayClasses = {}; var arrayClass = function(baseClass, ndim) { ndim || (ndim = 1); var stub = Clazz._getParamCode(baseClass); + var key = stub + ";" + ndim; + var ret = arrayClasses[key]; + if (ret) + return ret; while (aas.length < ndim) aas += aas; var aaa = aas.substring(0, ndim); var o = {}; + var a = new Array(ndim); o.arrayType = 1; o.__BASECLASS = baseClass; o.__NDIM = ndim; @@ -847,9 +1007,6 @@ var arrayClass = function(baseClass, ndim) { case "S": stub = "String"; break; - case "H": - stub = "S"; - break; default: if (stub.length > 1) stub = baseClass.__CLASS_NAME__; @@ -861,6 +1018,7 @@ var arrayClass = function(baseClass, ndim) { stub = "Ljava.lang." + stub + ";"; return aaa.replace(/A/g,"[") + stub; })())}; + arrayClasses[key] = oclass; return oclass; } @@ -1312,14 +1470,19 @@ var extendObject = function(clazz, ext) { // return false; //}; +// see also var excludeSuper = function(o) { return o == "b$" || o == "$this$0" || o == "$init$" || o == "$init0$" || o == "$static$" || o == "$clinit$" + || o == "$classes$" + || o == "$fields$" || o == "$load$" || o == "$Class$" + || o == "$getMembers$" + || o == "$getAnn$" || o == "prototype" || o == "__PARAMCODE" || o == "__CLASS_NAME__" @@ -1359,8 +1522,8 @@ var finalizeClazz = function(clazz, qname, bname, type, isNumber) { (type == 1) && (clazz.__ANON = clazz.prototype.__ANON = 1); (type == 2) && (clazz.__LOCAL = clazz.prototype.__LOCAL = 1); - if (!isNumber && type != 0) - Clazz.newMeth(clazz, '$init0$', function(){var c;if ((c=clazz.superclazz) && (c = c.$init0$))c.apply(this);}, 1); +// if (!isNumber && type != 0) +// Clazz.newMeth(clazz, '$init0$', function(){var c;if ((c=clazz.superclazz) && (c = c.$init0$))c.apply(this);}, 1); if (isNumber || type != 0) extendPrototype(clazz); @@ -1769,11 +1932,9 @@ Clazz.newMeth(C$, 'chars$', function () { return $I$(1).intStream$java_util_function_Supplier$I$Z(((P$.CharSequence$lambda1|| (function(){var C$=Clazz.newClass(P$, "CharSequence$lambda1", function(){Clazz.newInstance(this, arguments[0],1,C$);}, null, 'java.util.function.Supplier', 1); -C$.$clinit$ = function() {Clazz.load(C$, 1); -} +C$.$clinit$ = 1; -Clazz.newMeth(C$, '$init$', function () { -}, 1); +Clazz.newMeth(C$, '$init$', function () {}, 1); /*lambda_E*/ Clazz.newMeth(C$, 'get$', function () { return($I$(2).spliterator$java_util_PrimitiveIterator_OfInt$J$I(Clazz.new_(CharSequence$1CharIterator.$init$, [this, null]), this.b$['CharSequence'].length$(), 16));}); })() @@ -1784,8 +1945,7 @@ Clazz.newMeth(C$, 'codePoints$', function () { return $I$(1).intStream$java_util_function_Supplier$I$Z(((P$.CharSequence$lambda2|| (function(){var C$=Clazz.newClass(P$, "CharSequence$lambda2", function(){Clazz.newInstance(this, arguments[0],1,C$);}, null, 'java.util.function.Supplier', 1); -C$.$clinit$ = function() {Clazz.load(C$, 1); -} +C$.$clinit$ = 1; Clazz.newMeth(C$, '$init$', function () { }, 1); @@ -1799,8 +1959,7 @@ Clazz.newMeth(C$, 'get$', function () { return($I$(2).spliteratorUnknownSize$jav Clazz.newInstance(this, arguments[0],true,C$); }, null, [['java.util.PrimitiveIterator','java.util.PrimitiveIterator.OfInt']], 2); -C$.$clinit$ = function() {Clazz.load(C$, 1); -} +C$.$clinit$ = 1; Clazz.newMeth(C$, '$init0$', function () { var c;if((c = C$.superclazz) && (c = c.$init0$))c.apply(this); @@ -1835,8 +1994,7 @@ Clazz.newMeth(C$); Clazz.newInstance(this, arguments[0],true,C$); }, null, [['java.util.PrimitiveIterator','java.util.PrimitiveIterator.OfInt']], 2); -C$.$clinit$ = function() {Clazz.load(C$, 1); -} +C$.$clinit$ = 1; Clazz.newMeth(C$, '$init0$', function () { var c;if((c = C$.superclazz) && (c = c.$init0$))c.apply(this); @@ -1957,6 +2115,9 @@ Clazz._Loader = function () {}; ;(function(Clazz, _Loader) { +// The class loader is always accessed through Class. +// See Class.java for implementations of the methods of java.lang.ClassLoader such as getSystemResource and getResource + java.lang.ClassLoader = _Loader; // BH windows-level only because it's java.lang ClassLoader = _Loader; @@ -1973,7 +2134,19 @@ _Loader.getSystemClassLoader$ = function() { var assertionStatus = {}; -_Loader.getClassAssertionStatus$ = function(clazz) { +_Loader.getSystemResource$S = function(name) { + return _Loader.getSystemClassLoader$().getResource$(name); +} + +_Loader.getSystemResources$S = function(name) { + return _Loader.getSystemClassLoader$().getResources$(name); +} + +_Loader.getSystemResourceAsStream$S = function(name) { + return _Loader.getSystemClassLoader$().getResourceAsStream$(name); +} + +_Loader.getClassAssertionStatus$ = function(clazz) { // harmony var ret; var clazzName = clazz.__CLASS_NAME__ + "."; for (var c in assertionStatus) { @@ -2005,6 +2178,8 @@ _Loader.prototype.setClassAssertionStatus$S$Z = _Loader.prototype.setPackageAsse _Loader.prototype.loadClass$S = function(clazzName) { return Clazz.forName(clazzName); } + + _Loader._checkLoad = J2S._checkLoad; _Loader._TODO = []; @@ -2336,7 +2511,7 @@ var evaluate = function(file, js) { Clazz._initClass = function(c,clinit,status,objThis) { var f; - return clinit && (f=c.$clinit$) && (f === 1 ? Clazz.load(c,1) : f ? f() : 0), + return clinit && (f=c.$clinit$) && (f === 1 || f === 2 ? Clazz.load(c,1) : f && typeof f == "function"? f() : 0), status && c.$load$ && Clazz.load(c, 2), objThis && (f=c.$init0$) && f.apply(objThis), c; @@ -2858,169 +3033,224 @@ Clazz.alert = function (s) { })(Clazz.Console); +var getURIField = function(name, def) { + try { + var ref = document.location.href.toLowerCase(); + var i = ref.indexOf(name.toLowerCase() + "="); + if (i >= 0) + def = (document.location.href + "&").substring( + i + name.length + 1).split("&")[0]; + } catch (e) { + } finally { + return def; + } +} -Clazz._setDeclared("java.lang.System", -java.lang.System = System = { - props : null, //new java.util.Properties (), - $props : {}, - arraycopy$O$I$O$I$I : function (src, srcPos, dest, destPos, length) { - if (src !== dest || srcPos > destPos) { - for (var i = length; --i >= 0;) - dest[destPos++] = src[srcPos++]; - } else { - destPos += length; - srcPos += length; - for (var i = length; --i >= 0;) - src[--destPos] = src[--srcPos]; - } - }, - - currentTimeMillis$ : function() { - return new Date().getTime(); - }, - exit$ : function(status) { - java.lang.Runtime || Clazz.loadClass("java.lang.Runtime"); - java.lang.Runtime.getRuntime$().exit$I(status || 0); - }, - gc$ : function() {}, // bh - getProperties$ : function() { - return System.props; - }, - getProperty$S$S : function(key, def) { - if (System.props) - return System.props.getProperty$S$S (key, def); - var v = System.$props[key]; - if (typeof v != "undefined") - return v; - if (key.indexOf(".") > 0) { - v = null; - switch (key) { - case "java.awt.printerjob": - v = "swingjs.JSPrinterJob"; - break; - case "java.class.version": - v = "50"; - break; - case "user.home": - v = "https://."; - break; - case "java.vendor": - v = "SwingJS/OpenJDK"; - break; - case "java.version": - v = "1.6-1.8"; - break; - case "file.separator": - case "path.separator": - v = "/"; - break; - case "line.separator": - v = (navigator.userAgent.indexOf("Windows") >= 0 ? "\r\n" : "\n"); - break; - case "os.name": - case "os.version": - v = navigator.userAgent; - break; - case "javax.xml.datatype.DatatypeFactory": - v = "swingjs.xml.JSJAXBDatatypeFactory"; - break; - case "javax.xml.bind.JAXBContextFactory": - v = "swingjs.xml.JSJAXBContextFactory"; - break; - } - if (v) - return System.$props[key] = v; - } - return (arguments.length == 1 ? null : def == null ? key : def); // BH - }, - getSecurityManager$ : function() { return null }, // bh - identityHashCode$O : function(obj){return obj==null ? 0 : obj._$hashcode || (obj._$hashcode = ++hashCode)}, - lineSeparator$ : function() { return '\n' }, // bh - nanoTime$: function() { - return Math.round(window.performance.now() * 1e6) - }, - setProperties$java_util_Properties : function (props) { - System.props = props; - }, - setProperty$S$S : function (key, val) { - if (!System.props) - return System.$props[key] = val; // BH - System.props.setProperty (key, val); - } - -}); +var sysprops = { + "file.separator" : "/", + "line.separator" : "\n", + "java.awt.printerjob" : "swingjs.JSPrinterJob", + "java.class.path" : "/", + "java.class.version" : "80", + "java.home" : "https://.", + "java.vendor" : "java2script/SwingJS/OpenJDK", + "java.vendor.url" : "https://github.com/BobHanson/java2script", + "java.version" : "1.8", + "os.arch" : navigator.userAgent, + "os.name" : navigator.userAgent, + "os.version": navigator.userAgent, + "path.separator" : ":", + "user.dir" : "https://.", + "user.home" : "https://.", + "user.name" : "user", + "javax.xml.datatype.DatatypeFactory" : "swingjs.xml.JSJAXBDatatypeFactory", + "javax.xml.bind.JAXBContextFactory" : "swingjs.xml.JSJAXBContextFactory" +} + +Clazz._setDeclared("java.lang.System", java.lang.System = System = {}); +;(function(C$){ + +C$.lineSeparator = "\n"; +C$.props = null; + +C$.setIn$java_io_InputStream=function ($in) { + C$.$in=$in; +} + +C$.setOut$java_io_PrintStream=function (out) { + C$.out=out; +} + +C$.setErr$java_io_PrintStream=function (err) { + C$.err=err; +} + +C$.console$=function () { + return null; +} + +C$.inheritedChannel$=function () { + return null; +} + +C$.setSecurityManager$SecurityManager=function (s) { +} + +C$.getSecurityManager$=function () { + return null; +} + +C$.currentTimeMillis$=function () { + { + return new Date().getTime(); +} +} + +C$.nanoTime$=function () { + { + return Math.round(window.performance.now() * 1e6); +} +} + +C$.arraycopy$O$I$O$I$I=function (src, srcPos, dest, destPos, length) { + + if (src !== dest || srcPos > destPos) { for (var i = length; --i >= 0;) dest[destPos++] = src[srcPos++]; } else { destPos += length; srcPos += length; for (var i = length; --i >= 0;) src[--destPos] = src[--srcPos]; } +} + +C$.identityHashCode$O=function (x) { + return x==null ? 0 : x._$hashcode || (x._$hashcode = ++hashCode); +} + +C$.getProperties$=function () { + if (C$.props == null ) + C$.props=Clazz.new_("java.util.Properties"); + for (a in sysprops) + C$.props.put$TK$TV(a, sysprops[a]); + return C$.props; +} + +C$.lineSeparator$=function () { + return C$.lineSeparator; +} + +C$.setProperties$java_util_Properties=function (props) { + C$.props = props; +} + +C$.getProperty$S=function (key) { + C$.checkKey$S(key); + return (C$.props == null ? sysprops[key] : C$.props.getProperty$S(key)); +} + +C$.getProperty$S$S=function (key, def) { + C$.checkKey$S(key); + if (C$.props == null) { + var prop = sysprops[key]; + return (prop == null ? def : prop); + } + return C$.props.getProperty$S$S(key, def); +} + +C$.setProperty$S$S=function (key, value) { + C$.checkKey$S(key); + var ret; + if (C$.props == null) { + ret = sysprops[key]; + sysprops[key] = value; + return ret || null; + } + return C$.props.setProperty$S$S(key, value); +} + +C$.clearProperty$S=function (key) { + C$.checkKey$S(key); + return (C$.props == null ? null : C$.props.remove$O(key)); +} + +C$.checkKey$S=function (key) { + if (key == null ) { + throw Clazz.new_(Clazz.load('NullPointerException').c$$S,["key can\'t be null"]); + }if (key.equals$O("")) { + throw Clazz.new_(Clazz.load('IllegalArgumentException').c$$S,["key can\'t be empty"]); + } +} + +C$.getenv$S=function (name) { + var s = J2S.getGlobal(name) || getURIField(name, null); + return s || null; +} + +var env = null; + +C$.getenv$=function () { + return env || (env = Clazz.load("java.util.Properties")); +} + +C$.exit$I=function (status) { + Clazz.loadClass("java.lang.Runtime").getRuntime$().exit$I(status | 0); +} + +C$.gc$=C$.runFinalization$=C$.runFinalizersOnExit$Z=C$.load$S=C$.loadLibrary$S=C$.mapLibraryName$S= + function (libname) {return null;} + +})(System); ;(function(Con, Sys) { -Sys.getProperty$S = Sys.getProperty$S$S; -Sys.exit$I = Sys.exit$; +Sys.exit$ = Sys.exit$I; Sys.out = new Clazz._O (); Sys.out.__CLASS_NAME__ = "java.io.PrintStream"; +Sys.err = new Clazz._O (); +Sys.err.__CLASS_NAME__ = "java.io.PrintStream"; Sys.setOut$java_io_PrintStream = function(ps) { - Sys.out = ps; + System.out = ps; }; Sys.setErr$java_io_PrintStream = function(ps) { - Sys.err = ps; + System.err = ps; }; +var checkTrace = function(s) { + if (J2S._nooutput || J2S._traceFilter && s.indexOf(J2S._traceFilter) < 0) return; + if (!J2S._traceFilter && J2S._traceOutput && s && + (("" + s).indexOf(J2S._traceOutput) >= 0 || '"' + s + '"' == J2S._traceOutput)) { + alert(s + "\n\n" + Clazz._getStackTrace()); + doDebugger(); + } +} -Sys.out.print = Sys.out.print$O = Sys.out.print$Z = Sys.out.print$I = Sys.out.println$J = Sys.out.print$S = Sys.out.print$C = Sys.out.print = function (s) { +var setps = function(ps, f) { - Con.consoleOutput (s); +ps.print = ps.print$O = ps.print$Z = ps.print$I = ps.println$J = ps.print$S = ps.print$C = ps.print = function (s) { + checkTrace(s); + f(s); }; -Sys.out.printf = Sys.out.printf$S$OA = Sys.out.format = Sys.out.format$S$OA = function (f, args) { - Sys.out.print(String.format$S$OA.apply(null, arguments)); +ps.printf = ps.printf$S$OA = ps.format = ps.format$S$OA = function (f, args) { + ps.print(String.format$S$OA.apply(null, arguments)); } -Sys.out.flush$ = function() {} +ps.flush$ = function() {} -Sys.out.println = Sys.out.println$ = Sys.out.println$O = Sys.out.println$Z = Sys.out.println$I = Sys.out.println$J = Sys.out.println$S = Sys.out.println$C = function(s) { +ps.println = ps.println$ = ps.println$O = ps.println$Z = ps.println$I = ps.println$J = ps.println$S = ps.println$C = function(s) { s = (typeof s == "undefined" ? "" : "" + s); - if (J2S._nooutput || J2S._traceFilter && s.indexOf(J2S._traceFilter) < 0) return; - if (!J2S._traceFilter && J2S._traceOutput && s && - (s.indexOf(J2S._traceOutput) >= 0 || '"' + s + '"' == J2S._traceOutput)) { - alert(s + "\n\n" + Clazz._getStackTrace()); - doDebugger(); - } - Con.consoleOutput(typeof s == "undefined" ? "\r\n" : s == null ? s = "null\r\n" : s + "\r\n"); -}; - -Sys.out.println$F = Sys.out.println$D = function(f) {var s = "" + f; Sys.out.println(s.indexOf(".") < 0 && s.indexOf("Inf") < 0 ? s + ".0" : s)}; - -Sys.out.write$BA$I$I = function (buf, offset, len) { - Sys.out.print(String.instantialize(buf).substring(offset, offset+len)); + checkTrace(s); + s = (typeof s == "undefined" ? "\r\n" : s == null ? s = "null\r\n" : s + "\r\n"); + f(s); }; -Sys.err = new Clazz._O (); -Sys.err.__CLASS_NAME__ = "java.io.PrintStream"; +ps.println$F = ps.println$D = function(f) {var s = "" + f; ps.println(s.indexOf(".") < 0 && s.indexOf("Inf") < 0 ? s + ".0" : s)}; -Sys.err.print = Sys.err.print$S = function (s) { - Con.consoleOutput (s, "red"); +ps.write$BA$I$I = function (buf, offset, len) { + ps.print(String.instantialize(buf).substring(offset, offset+len)); }; -Sys.err.printf = Sys.err.printf$S$OA = Sys.err.format = Sys.err.format$S$OA = Sys.err.format = function (f, args) { - Sys.out.print(String.format$S$OA.apply(null, arguments)); } -Sys.err.println = Sys.err.println$O = Sys.err.println$Z = Sys.err.println$I = Sys.err.println$S = Sys.err.println$C = Sys.err.println = function (s) { - if (Clazz._traceOutput && s && ("" + s).indexOf(Clazz._traceOutput) >= 0) { - alert(s + "\n\n" + Clazz._getStackTrace()); - doDebugger(); - } - Con.consoleOutput (typeof s == "undefined" ? "\r\n" : s == null ? s = "null\r\n" : s + "\r\n", "red"); -}; - -Sys.err.println$F = Sys.err.println$D = function(f) {var s = "" + f; Sys.err.println(s.indexOf(".") < 0 && s.indexOf("Inf") < 0 ? s + ".0" : s)}; - -Sys.err.write$BA$I$I = function (buf, offset, len) { - Sys.err.print(String.instantialize(buf).substring(offset, offset+len)); -}; - -Sys.err.flush$ = function() {} +setps(Sys.out, function(s) {Con.consoleOutput(s)}); +setps(Sys.err, function(s) {Con.consoleOutput(s, "red")}); })(Clazz.Console, System); @@ -3229,18 +3459,25 @@ if (typeof arguments[0] != "object")this.c$(arguments[0]); var primTypes = {}; +var FALSE = function() { return false }; +var EMPTY_CLASSES = function() {return Clazz.array(Class, [0])}; +var NULL_FUNC = function() {return null}; + var setJ2STypeclass = function(cl, type, paramCode) { // TODO -- should be a proper Java.lang.Class primTypes[paramCode] = cl; cl.TYPE = { - isArray$: function() { return false }, isPrimitive$: function() { return true }, type:type, __PARAMCODE:paramCode, __PRIMITIVE:1 // referenced in java.lang.Class }; + cl.TYPE.isArray$ = cl.TYPE.isEnum$ = cl.TYPE.isAnnotation$ = FALSE; cl.TYPE.toString = cl.TYPE.getName$ = cl.TYPE.getTypeName$ - = cl.TYPE.getCanonicalName$ = cl.TYPE.getSimpleName$ = function() {return type} + = cl.TYPE.getCanonicalName$ = cl.TYPE.getSimpleName$ = function() {return type}; + cl.TYPE.isAssignableFrom$Class = (function(t) {return function(c) {return c == t}})(cl.TYPE); + cl.TYPE.getSuperclass$ = NULL_FUNC; + cl.TYPE.getInterfaces$ = EMPTY_CLASSES; } var decorateAsNumber = function (clazz, qClazzName, type, PARAMCODE) { @@ -3674,6 +3911,8 @@ if(s==null){ throw Clazz.new_(NumberFormatException.c$$S, ["null"]); } if (typeof s == "number")return s; // important -- typeof NaN is "number" and is OK here +if (s == "NaN") + return NaN; var floatVal=Number(s); if(isNaN(floatVal)){ throw Clazz.new_(NumberFormatException.c$$S, ["Not a Number : "+s]); @@ -3746,6 +3985,8 @@ if(s==null){ throw Clazz.new_(NumberFormatException.c$$S, ["null"]); } if (typeof s == "number")return s; // important -- typeof NaN is "number" and is OK here +if (s == "NaN") + return NaN; var doubleVal=Number(s); if(isNaN(doubleVal)){ throw Clazz.new_(NumberFormatException.c$$S, ["Not a Number : "+s]); @@ -4143,6 +4384,15 @@ var getChars = function(s, srcBegin,srcEnd,dst,dstBegin, asBytes){ //var charset=["utf-8","utf8","us-ascii","iso-8859-1","8859_1","gb2312","gb18030"]; var charset=["utf-8","utf8","us-ascii","iso-8859-1"]; // gb* uses GBK + +sp.getBytes$I$I$BA$I=function(i0, i1, dst, dpt) { + if (i1 == i0) + return; + var s = this.valueOf(); + for (var i = i0; i < i1; i++) + dst[dpt++] = s.charCodeAt(i); +} + sp.getBytes$=sp.getBytes$S=sp.getBytes$java_nio_charset_Charset=function(){ var s=this; if(arguments.length==1){ @@ -4464,9 +4714,10 @@ String.join$CharSequence$CharSequenceA = function(sep,array) { return ret; } -String.join$CharSequence$Iterable = function(sep,iter) { +String.join$CharSequence$Iterable = function(sep,iterable) { var ret = ""; var s = ""; + var iter = iterable.iterator$(); while (iter.hasNext$()) { ret += s + iter.next$().toString(); s || (s = sep); @@ -4879,27 +5130,31 @@ return this.stackTrace; }); m$(C$, 'printStackTrace$', function () { - printStackTrace(this, System.err.println$O); + printStackTrace(this, System.err); }); -var printStackTrace = function(e, f) { -f("" + e); +m$(C$, 'printStackTrace$java_io_PrintWriter', function (writer) { + printStackTrace(this, writer); +}); + +var printStackTrace = function(e, ps) { +ps.println$O("" + e); if (!e.stackTrace){ - f(e.stack); + ps.println$O(e.stack); return; } for (var i = 0; i < e.stackTrace.length; i++) { var t = e.stackTrace[i]; if (t.nativeClazz == null || isInstanceOf(t.nativeClazz, Throwable) < 0) { - f(t); + ps.println$O(t); } } // from a JavaScript error -e.stack && f(e.stack); +e.stack && ps.println$O(e.stack); } m$(C$, ['printStackTrace$java_io_PrintStream','printStackTrace$java_io_PrintWriter'], function (stream) { - printStackTrace(this, stream.println$O); + printStackTrace(this, stream); }); Clazz.newMeth(C$, 'fillInStackTrace$', function () { @@ -5040,7 +5295,7 @@ TypeError.prototype.getMessage$ || (TypeError.prototype.getMessage$ = TypeError. = function(){ return (this.stack ? this.stack : this.message || this.toString()) + (this.getStackTrace ? this.getStackTrace$() : Clazz._getStackTrace())}); TypeError.prototype.printStackTrace$ = function(){System.out.println(this + "\n" + this.stack)}; TypeError.prototype.printStackTrace$java_io_PrintStream = function(stream){stream.println$S(this + "\n" + this.stack);}; - +TypeError.prototype.printStackTrace$java_io_PrintWriter = function(printer){printer.println$S(this + "\n" + this.stack);}; Clazz.Error = Error; var declareType = function(prefix, name, clazzSuper, interfacez) { @@ -5055,7 +5310,7 @@ Clazz._Error || (Clazz._Error = Error); //setSuperclass(Clazz._Error, Throwable); var setEx = function(C$) { - C$.$clinit$ = function() {Clazz.load(C$, 1)}; + C$.$clinit$ = 1; m$(C$, "c$", function() { C$.superclazz.c$.apply(this, []);}, 1); m$(C$, "c$$S", function(detailMessage){C$.superclazz.c$$S.apply(this,[detailMessage]);},1); m$(C$, "c$$Throwable", function(exception){C$.superclazz.c$$Throwable.apply(this, arguments);}, 1); @@ -5271,7 +5526,7 @@ var C$=Clazz.newClass(java.util,"MissingResourceException",function(){ this.className=null; this.key=null; },RuntimeException); -C$.$clinit$ = function() {Clazz.load(C$, 1)}; +C$.$clinit$ = 1; m$(C$, "c$$S$S$S", function(detailMessage,className,resourceName){ Clazz.super_(C$, this); C$.superclazz.c$$S.apply(this,[detailMessage]); @@ -5294,71 +5549,6 @@ setJ2STypeclass(java.lang.Void, "void", "V"); //java.lang.V Clazz.newInterface(java.lang.reflect,"GenericDeclaration"); -Clazz.newInterface(java.lang.reflect,"AnnotatedElement"); - -;(function() { -var C$=declareType(java.lang.reflect,"AccessibleObject",null,java.lang.reflect.AnnotatedElement); -m$(C$, "c$",function(){ -}, 1); -m$(C$,"isAccessible$", -function(){ -return true; -}); -m$(C$,"setAccessible$reflect_AccessibleObjectA$Z", -function(objects,flag){ -return; -},1); -m$(C$,"setAccessible$Z", -function(flag){ -return; -}); -m$(C$,"isAnnotationPresent$Class", -function(annotationType){ -return false; -}); -m$(C$,"getDeclaredAnnotations$", -function(){ -return new Array(0); -}); -m$(C$,"getAnnotations$", -function(){ -return new Array(0); -}); -m$(C$,"getAnnotation$Class", -function(annotationType){ -return null; -}); -m$(C$,"marshallArguments$ClassA$OA", -function(parameterTypes,args){ -return null; -}, 1); -m$(C$,"invokeV$O$OA", -function(receiver,args){ -return; -}); -m$(C$,"invokeL$O$OA", -function(receiver,args){ -return null; -}); -m$(C$,"invokeI$O$OA", -function(receiver,args){ -return 0; -}); -m$(C$,"invokeJ$O$OA", -function(receiver,args){ -return 0; -}); -m$(C$,"invokeF$O$OA", -function(receiver,args){ -return 0.0; -}); -m$(C$,"invokeD$O$OA", -function(receiver,args){ -return 0.0; -}); -C$.emptyArgs=C$.prototype.emptyArgs=new Array(0); -})(); - Clazz.newInterface(java.lang.reflect,"InvocationHandler"); diff --git a/sources/net.sf.j2s.java.core/srcjs/swingjs2.js b/sources/net.sf.j2s.java.core/srcjs/swingjs2.js index 164636536..7a1da597e 100644 --- a/sources/net.sf.j2s.java.core/srcjs/swingjs2.js +++ b/sources/net.sf.j2s.java.core/srcjs/swingjs2.js @@ -11878,7 +11878,8 @@ console.log("J2S._getRawDataFromServer " + J2S._serverUrl + " for " + query); return Clazz.loadClass("javajs.util.Base64").decodeBase64$S( s.substring(8)); } - // not UTF-8 + // return Clazz.array(new Int8Array(new TextEncoder().encode(s).buffer),Clazz.array(Byte.TYPE)); + // not UTF-8 - this is a Chrome encoding of the bytes as "string" var b = Clazz.array(Byte.TYPE, s.length); for (var i = s.length; --i >= 0;) b[i] = s.charCodeAt(i) & 0xFF; @@ -13863,6 +13864,10 @@ if (ev.keyCode == 9 && ev.target["data-focuscomponent"]) { // Google closure compiler cannot handle Clazz.new or Clazz.super +// BH 2019.12.29 3.2.6 fixes Float.parseFloat$S("NaN") [and Double] +// BH 2019.12.23 3.2.6 update of System +// BH 2019.12.19 3.2.6 revision of $clinit$ +// BH 2019.12.16 3.2.5.v4 adds ClassLoader static methods for system resources (just j2s/...) // BH 2019.12.15 3.2.5.v4 Character.prototype.valueOf() missing // BH 2019.12.14 3.2.5.v3 Clazz._4Name initialization should be full static initialization @@ -13919,7 +13924,7 @@ Clazz.ClassFilesLoaded = []; Clazz.popup = Clazz.log = Clazz.error = window.alert; /* can be set by page JavaScript */ -Clazz.defaultAssertionStatus = true; +Clazz.defaultAssertionStatus = false; /* can be set by page JavaScript */ Clazz._assertFunction = null; @@ -13969,7 +13974,8 @@ var _array = function(baseClass, paramType, ndims, params, isClone) { return copyArrayProps(a, b); } if (arguments.length == 2 && baseClass.BYTES_PER_ELEMENT) { - // direct transfer of parameters from java.nio.ByteBuffer + // Clazz.array(rawInt8Array, int[] array) + // direct transfer of __* metadata (see java.nio.ByteBuffer) return copyArrayProps(paramType, baseClass); } var prim = Clazz._getParamCode(baseClass); @@ -14202,10 +14208,11 @@ Clazz.instanceOf = function (obj, clazz) { // unwrap java.lang.Class to JavaScript clazz using $clazz$ if (typeof clazz == "string") { clazz = Clazz._getDeclared(clazz); - } - + } if (obj == null || !clazz) return false; + if (obj == clazz) + return true; // check for object being a java.lang.Class and the other not if (obj.$clazz$ && !clazz.$clazz$) return (clazz == java.lang.Class); obj.$clazz$ && (obj = obj.$clazz$); @@ -14242,10 +14249,21 @@ Clazz.load = function(cName, from$clinit$) { if (from$clinit$ == 1) { // C$.$clinit$ call to finalize all dependencies var cl = cName; - cl.$clinit$ = 0; + cl.$clinit$ = 0-cl.$clinit$; + // -2 means v 3.2.6 + // -1 means v 3.2.5 + // NaN means original 3.2.4 function() {Clazz.load(C$, 1)}; var ld = cl.$load$; setSuperclass(cl, (ld && ld[0] ? Clazz.load(ld[0]) : null)); ld[1] && addInterface(cl, ld[1]); + switch (cl.$clinit$) { + case -1: + // done + break; + case -2: + initClass0(cl); + break; + } return; } if (from$clinit$ == 2) { @@ -14277,6 +14295,132 @@ Clazz.load = function(cName, from$clinit$) { return Clazz._4Name(cName, null, null, true); } +//create and $init0$ +var initClass0 = function(c) { + var fields = c.$fields$; + var objects = fields && fields[0]; + createDefaults(c, objects, false); + if (!fields) + return; + var statics = fields[1]; + if (statics && statics.length) + createDefaults(c, statics, true); +} + +//C$.$fields$=[ +//['I',['test3','itype'],'S',['test1'],'O',['test2','java.util.List[]','test4','test.Test_','+test5']], +//['D',['d'],'F',['f'],'I',['itest1','itest2'],'S',['stest1']] +//] +var createDefaults = function(c, data, isStatic) { + var a = getFields(c, data, true); + if (isStatic) { + for (var i = a.length; --i >= 0;) { + c[a[i][0]] = a[i][1]; + } + } else { + c.$init0$ = + //(function(cs, a) {return + function(){ + var cs = c.superclazz; + cs && cs.$init0$ && cs.$init0$.apply(this); + for (var i = a.length; --i >= 0;){ + this[a[i][0]] = a[i][1]; + } + }; +// })(c.superclazz, a); + } + +} + +Clazz._getFieldNames = function(c, isStatic) { + return (c.$fields$ ? getFields(c, c.$fields$[isStatic ? 1 : 0], 0) : []); +} + +Clazz._getFieldTypes = function(c, isStatic) { + return (c.$fields$ ? getFields(c, c.$fields$[isStatic ? 1 : 0], "types") : []); +} + +var fieldTypes = "Integer;Float;Double;Character;Long;Byte;" +/** + * Get correct default (0, '\0', null) or just + * return a list of names. + */ +var getFields = function(c, data, andDefaults) { + var a = []; + if (!data) + return a; + if (andDefaults == "types") { + for (var i = 0, n = data.length; i < n; i++) { + var type = data[i++]; + var anames = data[i]; + if (type != "O") { + for (var j = anames.length; --j >= 0;) + a.push(type); + continue; + } + type = "String"; + for (var j = 0, na = anames.length; j < na; j++) { + if (anames[j].indexOf("+") != 0) { + type = anames[++j]; + } + a.push(type); + } + continue; + } + return a; + } + if (andDefaults) { + for (var i = 0, n = data.length; i < n; i++) { + var type = data[i++]; + var anames = data[i]; + var defval; + switch (type) { + case 'S': + defval = null; + break; + case 'O': + for (var j = 0, na = anames.length; j < na; j++) { + var name = anames[j]; + if (name.indexOf("+") == 0) + name = name.substring(1); + else + j++; + a.push([name, null]); + } + continue; + case 'C': + defval = '\0'; + break; + default: + defval = 0; + break; + } + for (var j = 0, na = anames.length; j < na; j++) { + a.push([anames[j], defval]); + } + } + return a; + } + for (var i = 0, n = data.length; i < n; i++) { + var type = data[i++]; + var anames = data[i]; + if (type == 'O') { + for (var j = 0, na = anames.length; j < na; j++) { + var name = anames[j]; + if (name.indexOf("+") == 0) + name = name.substring(1); + else + j++; + a.push(name); + } + } else { + for (var j = 0, na = anames.length; j < na; j++) { + a.push(anames[j]); + } + } + } + return a; +} Clazz._newCount = 0; /** @@ -14292,6 +14436,16 @@ Clazz._newCount = 0; Clazz.new_ = function(c, args, cl) { if (!c) return new Clazz._O(); + var generics; + var a = arguments; + if (c === 1) { // new for 3.2.6 {K:"java.lang.String",...} + generics = arguments[1]; + a = []; + c = a[0] = arguments[2]; + args = a[1] = arguments[3]; + cl = a[2] = arguments[4]; + a = a.slice(0, arguments.length - 2); + } var haveArgs = !!args; args || (args = [[]]); @@ -14314,7 +14468,7 @@ Clazz.new_ = function(c, args, cl) { // or a static field or method call (which is handled // by the $I$(n) handler in the function initializer in // the newClass() call. - var obj = new (Function.prototype.bind.apply(cl, arguments)); + var obj = new (Function.prototype.bind.apply(cl, a)); if (args[2] != inheritArgs) { haveArgs && c.apply(obj, args); clInner && clInner.$init$.apply(obj); @@ -14322,6 +14476,9 @@ Clazz.new_ = function(c, args, cl) { _profileNew && addProfileNew(cl, window.performance.now() - t0); + if (generics) { + obj.$init$.generics = generics; + } return obj; } @@ -14674,17 +14831,21 @@ Clazz.super_ = function(cl, obj) { ///////////////////////////////////////////////////////////////////// var aas = "AAA"; -/** - * Create an array class placeholder for reflection - */ + +var arrayClasses = {}; var arrayClass = function(baseClass, ndim) { ndim || (ndim = 1); var stub = Clazz._getParamCode(baseClass); + var key = stub + ";" + ndim; + var ret = arrayClasses[key]; + if (ret) + return ret; while (aas.length < ndim) aas += aas; var aaa = aas.substring(0, ndim); var o = {}; + var a = new Array(ndim); o.arrayType = 1; o.__BASECLASS = baseClass; o.__NDIM = ndim; @@ -14703,9 +14864,6 @@ var arrayClass = function(baseClass, ndim) { case "S": stub = "String"; break; - case "H": - stub = "S"; - break; default: if (stub.length > 1) stub = baseClass.__CLASS_NAME__; @@ -14717,6 +14875,7 @@ var arrayClass = function(baseClass, ndim) { stub = "Ljava.lang." + stub + ";"; return aaa.replace(/A/g,"[") + stub; })())}; + arrayClasses[key] = oclass; return oclass; } @@ -15168,14 +15327,19 @@ var extendObject = function(clazz, ext) { // return false; //}; +// see also var excludeSuper = function(o) { return o == "b$" || o == "$this$0" || o == "$init$" || o == "$init0$" || o == "$static$" || o == "$clinit$" + || o == "$classes$" + || o == "$fields$" || o == "$load$" || o == "$Class$" + || o == "$getMembers$" + || o == "$getAnn$" || o == "prototype" || o == "__PARAMCODE" || o == "__CLASS_NAME__" @@ -15215,8 +15379,8 @@ var finalizeClazz = function(clazz, qname, bname, type, isNumber) { (type == 1) && (clazz.__ANON = clazz.prototype.__ANON = 1); (type == 2) && (clazz.__LOCAL = clazz.prototype.__LOCAL = 1); - if (!isNumber && type != 0) - Clazz.newMeth(clazz, '$init0$', function(){var c;if ((c=clazz.superclazz) && (c = c.$init0$))c.apply(this);}, 1); +// if (!isNumber && type != 0) +// Clazz.newMeth(clazz, '$init0$', function(){var c;if ((c=clazz.superclazz) && (c = c.$init0$))c.apply(this);}, 1); if (isNumber || type != 0) extendPrototype(clazz); @@ -15625,11 +15789,9 @@ Clazz.newMeth(C$, 'chars$', function () { return $I$(1).intStream$java_util_function_Supplier$I$Z(((P$.CharSequence$lambda1|| (function(){var C$=Clazz.newClass(P$, "CharSequence$lambda1", function(){Clazz.newInstance(this, arguments[0],1,C$);}, null, 'java.util.function.Supplier', 1); -C$.$clinit$ = function() {Clazz.load(C$, 1); -} +C$.$clinit$ = 1; -Clazz.newMeth(C$, '$init$', function () { -}, 1); +Clazz.newMeth(C$, '$init$', function () {}, 1); /*lambda_E*/ Clazz.newMeth(C$, 'get$', function () { return($I$(2).spliterator$java_util_PrimitiveIterator_OfInt$J$I(Clazz.new_(CharSequence$1CharIterator.$init$, [this, null]), this.b$['CharSequence'].length$(), 16));}); })() @@ -15640,8 +15802,7 @@ Clazz.newMeth(C$, 'codePoints$', function () { return $I$(1).intStream$java_util_function_Supplier$I$Z(((P$.CharSequence$lambda2|| (function(){var C$=Clazz.newClass(P$, "CharSequence$lambda2", function(){Clazz.newInstance(this, arguments[0],1,C$);}, null, 'java.util.function.Supplier', 1); -C$.$clinit$ = function() {Clazz.load(C$, 1); -} +C$.$clinit$ = 1; Clazz.newMeth(C$, '$init$', function () { }, 1); @@ -15655,8 +15816,7 @@ Clazz.newMeth(C$, 'get$', function () { return($I$(2).spliteratorUnknownSize$jav Clazz.newInstance(this, arguments[0],true,C$); }, null, [['java.util.PrimitiveIterator','java.util.PrimitiveIterator.OfInt']], 2); -C$.$clinit$ = function() {Clazz.load(C$, 1); -} +C$.$clinit$ = 1; Clazz.newMeth(C$, '$init0$', function () { var c;if((c = C$.superclazz) && (c = c.$init0$))c.apply(this); @@ -15691,8 +15851,7 @@ Clazz.newMeth(C$); Clazz.newInstance(this, arguments[0],true,C$); }, null, [['java.util.PrimitiveIterator','java.util.PrimitiveIterator.OfInt']], 2); -C$.$clinit$ = function() {Clazz.load(C$, 1); -} +C$.$clinit$ = 1; Clazz.newMeth(C$, '$init0$', function () { var c;if((c = C$.superclazz) && (c = c.$init0$))c.apply(this); @@ -15813,6 +15972,9 @@ Clazz._Loader = function () {}; ;(function(Clazz, _Loader) { +// The class loader is always accessed through Class. +// See Class.java for implementations of the methods of java.lang.ClassLoader such as getSystemResource and getResource + java.lang.ClassLoader = _Loader; // BH windows-level only because it's java.lang ClassLoader = _Loader; @@ -15829,7 +15991,19 @@ _Loader.getSystemClassLoader$ = function() { var assertionStatus = {}; -_Loader.getClassAssertionStatus$ = function(clazz) { +_Loader.getSystemResource$S = function(name) { + return _Loader.getSystemClassLoader$().getResource$(name); +} + +_Loader.getSystemResources$S = function(name) { + return _Loader.getSystemClassLoader$().getResources$(name); +} + +_Loader.getSystemResourceAsStream$S = function(name) { + return _Loader.getSystemClassLoader$().getResourceAsStream$(name); +} + +_Loader.getClassAssertionStatus$ = function(clazz) { // harmony var ret; var clazzName = clazz.__CLASS_NAME__ + "."; for (var c in assertionStatus) { @@ -15861,6 +16035,8 @@ _Loader.prototype.setClassAssertionStatus$S$Z = _Loader.prototype.setPackageAsse _Loader.prototype.loadClass$S = function(clazzName) { return Clazz.forName(clazzName); } + + _Loader._checkLoad = J2S._checkLoad; _Loader._TODO = []; @@ -16192,7 +16368,7 @@ var evaluate = function(file, js) { Clazz._initClass = function(c,clinit,status,objThis) { var f; - return clinit && (f=c.$clinit$) && (f === 1 ? Clazz.load(c,1) : f ? f() : 0), + return clinit && (f=c.$clinit$) && (f === 1 || f === 2 ? Clazz.load(c,1) : f && typeof f == "function"? f() : 0), status && c.$load$ && Clazz.load(c, 2), objThis && (f=c.$init0$) && f.apply(objThis), c; @@ -16714,169 +16890,224 @@ Clazz.alert = function (s) { })(Clazz.Console); +var getURIField = function(name, def) { + try { + var ref = document.location.href.toLowerCase(); + var i = ref.indexOf(name.toLowerCase() + "="); + if (i >= 0) + def = (document.location.href + "&").substring( + i + name.length + 1).split("&")[0]; + } catch (e) { + } finally { + return def; + } +} -Clazz._setDeclared("java.lang.System", -java.lang.System = System = { - props : null, //new java.util.Properties (), - $props : {}, - arraycopy$O$I$O$I$I : function (src, srcPos, dest, destPos, length) { - if (src !== dest || srcPos > destPos) { - for (var i = length; --i >= 0;) - dest[destPos++] = src[srcPos++]; - } else { - destPos += length; - srcPos += length; - for (var i = length; --i >= 0;) - src[--destPos] = src[--srcPos]; - } - }, - - currentTimeMillis$ : function() { - return new Date().getTime(); - }, - exit$ : function(status) { - java.lang.Runtime || Clazz.loadClass("java.lang.Runtime"); - java.lang.Runtime.getRuntime$().exit$I(status || 0); - }, - gc$ : function() {}, // bh - getProperties$ : function() { - return System.props; - }, - getProperty$S$S : function(key, def) { - if (System.props) - return System.props.getProperty$S$S (key, def); - var v = System.$props[key]; - if (typeof v != "undefined") - return v; - if (key.indexOf(".") > 0) { - v = null; - switch (key) { - case "java.awt.printerjob": - v = "swingjs.JSPrinterJob"; - break; - case "java.class.version": - v = "50"; - break; - case "user.home": - v = "https://."; - break; - case "java.vendor": - v = "SwingJS/OpenJDK"; - break; - case "java.version": - v = "1.6-1.8"; - break; - case "file.separator": - case "path.separator": - v = "/"; - break; - case "line.separator": - v = (navigator.userAgent.indexOf("Windows") >= 0 ? "\r\n" : "\n"); - break; - case "os.name": - case "os.version": - v = navigator.userAgent; - break; - case "javax.xml.datatype.DatatypeFactory": - v = "swingjs.xml.JSJAXBDatatypeFactory"; - break; - case "javax.xml.bind.JAXBContextFactory": - v = "swingjs.xml.JSJAXBContextFactory"; - break; - } - if (v) - return System.$props[key] = v; - } - return (arguments.length == 1 ? null : def == null ? key : def); // BH - }, - getSecurityManager$ : function() { return null }, // bh - identityHashCode$O : function(obj){return obj==null ? 0 : obj._$hashcode || (obj._$hashcode = ++hashCode)}, - lineSeparator$ : function() { return '\n' }, // bh - nanoTime$: function() { - return Math.round(window.performance.now() * 1e6) - }, - setProperties$java_util_Properties : function (props) { - System.props = props; - }, - setProperty$S$S : function (key, val) { - if (!System.props) - return System.$props[key] = val; // BH - System.props.setProperty (key, val); - } - -}); +var sysprops = { + "file.separator" : "/", + "line.separator" : "\n", + "java.awt.printerjob" : "swingjs.JSPrinterJob", + "java.class.path" : "/", + "java.class.version" : "80", + "java.home" : "https://.", + "java.vendor" : "java2script/SwingJS/OpenJDK", + "java.vendor.url" : "https://github.com/BobHanson/java2script", + "java.version" : "1.8", + "os.arch" : navigator.userAgent, + "os.name" : navigator.userAgent, + "os.version": navigator.userAgent, + "path.separator" : ":", + "user.dir" : "https://.", + "user.home" : "https://.", + "user.name" : "user", + "javax.xml.datatype.DatatypeFactory" : "swingjs.xml.JSJAXBDatatypeFactory", + "javax.xml.bind.JAXBContextFactory" : "swingjs.xml.JSJAXBContextFactory" +} + +Clazz._setDeclared("java.lang.System", java.lang.System = System = {}); +;(function(C$){ + +C$.lineSeparator = "\n"; +C$.props = null; + +C$.setIn$java_io_InputStream=function ($in) { + C$.$in=$in; +} + +C$.setOut$java_io_PrintStream=function (out) { + C$.out=out; +} + +C$.setErr$java_io_PrintStream=function (err) { + C$.err=err; +} + +C$.console$=function () { + return null; +} + +C$.inheritedChannel$=function () { + return null; +} + +C$.setSecurityManager$SecurityManager=function (s) { +} + +C$.getSecurityManager$=function () { + return null; +} + +C$.currentTimeMillis$=function () { + { + return new Date().getTime(); +} +} + +C$.nanoTime$=function () { + { + return Math.round(window.performance.now() * 1e6); +} +} + +C$.arraycopy$O$I$O$I$I=function (src, srcPos, dest, destPos, length) { + + if (src !== dest || srcPos > destPos) { for (var i = length; --i >= 0;) dest[destPos++] = src[srcPos++]; } else { destPos += length; srcPos += length; for (var i = length; --i >= 0;) src[--destPos] = src[--srcPos]; } +} + +C$.identityHashCode$O=function (x) { + return x==null ? 0 : x._$hashcode || (x._$hashcode = ++hashCode); +} + +C$.getProperties$=function () { + if (C$.props == null ) + C$.props=Clazz.new_("java.util.Properties"); + for (a in sysprops) + C$.props.put$TK$TV(a, sysprops[a]); + return C$.props; +} + +C$.lineSeparator$=function () { + return C$.lineSeparator; +} + +C$.setProperties$java_util_Properties=function (props) { + C$.props = props; +} + +C$.getProperty$S=function (key) { + C$.checkKey$S(key); + return (C$.props == null ? sysprops[key] : C$.props.getProperty$S(key)); +} + +C$.getProperty$S$S=function (key, def) { + C$.checkKey$S(key); + if (C$.props == null) { + var prop = sysprops[key]; + return (prop == null ? def : prop); + } + return C$.props.getProperty$S$S(key, def); +} + +C$.setProperty$S$S=function (key, value) { + C$.checkKey$S(key); + var ret; + if (C$.props == null) { + ret = sysprops[key]; + sysprops[key] = value; + return ret || null; + } + return C$.props.setProperty$S$S(key, value); +} + +C$.clearProperty$S=function (key) { + C$.checkKey$S(key); + return (C$.props == null ? null : C$.props.remove$O(key)); +} + +C$.checkKey$S=function (key) { + if (key == null ) { + throw Clazz.new_(Clazz.load('NullPointerException').c$$S,["key can\'t be null"]); + }if (key.equals$O("")) { + throw Clazz.new_(Clazz.load('IllegalArgumentException').c$$S,["key can\'t be empty"]); + } +} + +C$.getenv$S=function (name) { + var s = J2S.getGlobal(name) || getURIField(name, null); + return s || null; +} + +var env = null; + +C$.getenv$=function () { + return env || (env = Clazz.load("java.util.Properties")); +} + +C$.exit$I=function (status) { + Clazz.loadClass("java.lang.Runtime").getRuntime$().exit$I(status | 0); +} + +C$.gc$=C$.runFinalization$=C$.runFinalizersOnExit$Z=C$.load$S=C$.loadLibrary$S=C$.mapLibraryName$S= + function (libname) {return null;} + +})(System); ;(function(Con, Sys) { -Sys.getProperty$S = Sys.getProperty$S$S; -Sys.exit$I = Sys.exit$; +Sys.exit$ = Sys.exit$I; Sys.out = new Clazz._O (); Sys.out.__CLASS_NAME__ = "java.io.PrintStream"; +Sys.err = new Clazz._O (); +Sys.err.__CLASS_NAME__ = "java.io.PrintStream"; Sys.setOut$java_io_PrintStream = function(ps) { - Sys.out = ps; + System.out = ps; }; Sys.setErr$java_io_PrintStream = function(ps) { - Sys.err = ps; + System.err = ps; }; +var checkTrace = function(s) { + if (J2S._nooutput || J2S._traceFilter && s.indexOf(J2S._traceFilter) < 0) return; + if (!J2S._traceFilter && J2S._traceOutput && s && + (("" + s).indexOf(J2S._traceOutput) >= 0 || '"' + s + '"' == J2S._traceOutput)) { + alert(s + "\n\n" + Clazz._getStackTrace()); + doDebugger(); + } +} -Sys.out.print = Sys.out.print$O = Sys.out.print$Z = Sys.out.print$I = Sys.out.println$J = Sys.out.print$S = Sys.out.print$C = Sys.out.print = function (s) { +var setps = function(ps, f) { - Con.consoleOutput (s); +ps.print = ps.print$O = ps.print$Z = ps.print$I = ps.println$J = ps.print$S = ps.print$C = ps.print = function (s) { + checkTrace(s); + f(s); }; -Sys.out.printf = Sys.out.printf$S$OA = Sys.out.format = Sys.out.format$S$OA = function (f, args) { - Sys.out.print(String.format$S$OA.apply(null, arguments)); +ps.printf = ps.printf$S$OA = ps.format = ps.format$S$OA = function (f, args) { + ps.print(String.format$S$OA.apply(null, arguments)); } -Sys.out.flush$ = function() {} +ps.flush$ = function() {} -Sys.out.println = Sys.out.println$ = Sys.out.println$O = Sys.out.println$Z = Sys.out.println$I = Sys.out.println$J = Sys.out.println$S = Sys.out.println$C = function(s) { +ps.println = ps.println$ = ps.println$O = ps.println$Z = ps.println$I = ps.println$J = ps.println$S = ps.println$C = function(s) { s = (typeof s == "undefined" ? "" : "" + s); - if (J2S._nooutput || J2S._traceFilter && s.indexOf(J2S._traceFilter) < 0) return; - if (!J2S._traceFilter && J2S._traceOutput && s && - (s.indexOf(J2S._traceOutput) >= 0 || '"' + s + '"' == J2S._traceOutput)) { - alert(s + "\n\n" + Clazz._getStackTrace()); - doDebugger(); - } - Con.consoleOutput(typeof s == "undefined" ? "\r\n" : s == null ? s = "null\r\n" : s + "\r\n"); -}; - -Sys.out.println$F = Sys.out.println$D = function(f) {var s = "" + f; Sys.out.println(s.indexOf(".") < 0 && s.indexOf("Inf") < 0 ? s + ".0" : s)}; - -Sys.out.write$BA$I$I = function (buf, offset, len) { - Sys.out.print(String.instantialize(buf).substring(offset, offset+len)); + checkTrace(s); + s = (typeof s == "undefined" ? "\r\n" : s == null ? s = "null\r\n" : s + "\r\n"); + f(s); }; -Sys.err = new Clazz._O (); -Sys.err.__CLASS_NAME__ = "java.io.PrintStream"; +ps.println$F = ps.println$D = function(f) {var s = "" + f; ps.println(s.indexOf(".") < 0 && s.indexOf("Inf") < 0 ? s + ".0" : s)}; -Sys.err.print = Sys.err.print$S = function (s) { - Con.consoleOutput (s, "red"); +ps.write$BA$I$I = function (buf, offset, len) { + ps.print(String.instantialize(buf).substring(offset, offset+len)); }; -Sys.err.printf = Sys.err.printf$S$OA = Sys.err.format = Sys.err.format$S$OA = Sys.err.format = function (f, args) { - Sys.out.print(String.format$S$OA.apply(null, arguments)); } -Sys.err.println = Sys.err.println$O = Sys.err.println$Z = Sys.err.println$I = Sys.err.println$S = Sys.err.println$C = Sys.err.println = function (s) { - if (Clazz._traceOutput && s && ("" + s).indexOf(Clazz._traceOutput) >= 0) { - alert(s + "\n\n" + Clazz._getStackTrace()); - doDebugger(); - } - Con.consoleOutput (typeof s == "undefined" ? "\r\n" : s == null ? s = "null\r\n" : s + "\r\n", "red"); -}; - -Sys.err.println$F = Sys.err.println$D = function(f) {var s = "" + f; Sys.err.println(s.indexOf(".") < 0 && s.indexOf("Inf") < 0 ? s + ".0" : s)}; - -Sys.err.write$BA$I$I = function (buf, offset, len) { - Sys.err.print(String.instantialize(buf).substring(offset, offset+len)); -}; - -Sys.err.flush$ = function() {} +setps(Sys.out, function(s) {Con.consoleOutput(s)}); +setps(Sys.err, function(s) {Con.consoleOutput(s, "red")}); })(Clazz.Console, System); @@ -17085,18 +17316,25 @@ if (typeof arguments[0] != "object")this.c$(arguments[0]); var primTypes = {}; +var FALSE = function() { return false }; +var EMPTY_CLASSES = function() {return Clazz.array(Class, [0])}; +var NULL_FUNC = function() {return null}; + var setJ2STypeclass = function(cl, type, paramCode) { // TODO -- should be a proper Java.lang.Class primTypes[paramCode] = cl; cl.TYPE = { - isArray$: function() { return false }, isPrimitive$: function() { return true }, type:type, __PARAMCODE:paramCode, __PRIMITIVE:1 // referenced in java.lang.Class }; + cl.TYPE.isArray$ = cl.TYPE.isEnum$ = cl.TYPE.isAnnotation$ = FALSE; cl.TYPE.toString = cl.TYPE.getName$ = cl.TYPE.getTypeName$ - = cl.TYPE.getCanonicalName$ = cl.TYPE.getSimpleName$ = function() {return type} + = cl.TYPE.getCanonicalName$ = cl.TYPE.getSimpleName$ = function() {return type}; + cl.TYPE.isAssignableFrom$Class = (function(t) {return function(c) {return c == t}})(cl.TYPE); + cl.TYPE.getSuperclass$ = NULL_FUNC; + cl.TYPE.getInterfaces$ = EMPTY_CLASSES; } var decorateAsNumber = function (clazz, qClazzName, type, PARAMCODE) { @@ -17530,6 +17768,8 @@ if(s==null){ throw Clazz.new_(NumberFormatException.c$$S, ["null"]); } if (typeof s == "number")return s; // important -- typeof NaN is "number" and is OK here +if (s == "NaN") + return NaN; var floatVal=Number(s); if(isNaN(floatVal)){ throw Clazz.new_(NumberFormatException.c$$S, ["Not a Number : "+s]); @@ -17602,6 +17842,8 @@ if(s==null){ throw Clazz.new_(NumberFormatException.c$$S, ["null"]); } if (typeof s == "number")return s; // important -- typeof NaN is "number" and is OK here +if (s == "NaN") + return NaN; var doubleVal=Number(s); if(isNaN(doubleVal)){ throw Clazz.new_(NumberFormatException.c$$S, ["Not a Number : "+s]); @@ -17999,6 +18241,15 @@ var getChars = function(s, srcBegin,srcEnd,dst,dstBegin, asBytes){ //var charset=["utf-8","utf8","us-ascii","iso-8859-1","8859_1","gb2312","gb18030"]; var charset=["utf-8","utf8","us-ascii","iso-8859-1"]; // gb* uses GBK + +sp.getBytes$I$I$BA$I=function(i0, i1, dst, dpt) { + if (i1 == i0) + return; + var s = this.valueOf(); + for (var i = i0; i < i1; i++) + dst[dpt++] = s.charCodeAt(i); +} + sp.getBytes$=sp.getBytes$S=sp.getBytes$java_nio_charset_Charset=function(){ var s=this; if(arguments.length==1){ @@ -18320,9 +18571,10 @@ String.join$CharSequence$CharSequenceA = function(sep,array) { return ret; } -String.join$CharSequence$Iterable = function(sep,iter) { +String.join$CharSequence$Iterable = function(sep,iterable) { var ret = ""; var s = ""; + var iter = iterable.iterator$(); while (iter.hasNext$()) { ret += s + iter.next$().toString(); s || (s = sep); @@ -18735,27 +18987,31 @@ return this.stackTrace; }); m$(C$, 'printStackTrace$', function () { - printStackTrace(this, System.err.println$O); + printStackTrace(this, System.err); +}); + +m$(C$, 'printStackTrace$java_io_PrintWriter', function (writer) { + printStackTrace(this, writer); }); -var printStackTrace = function(e, f) { -f("" + e); +var printStackTrace = function(e, ps) { +ps.println$O("" + e); if (!e.stackTrace){ - f(e.stack); + ps.println$O(e.stack); return; } for (var i = 0; i < e.stackTrace.length; i++) { var t = e.stackTrace[i]; if (t.nativeClazz == null || isInstanceOf(t.nativeClazz, Throwable) < 0) { - f(t); + ps.println$O(t); } } // from a JavaScript error -e.stack && f(e.stack); +e.stack && ps.println$O(e.stack); } m$(C$, ['printStackTrace$java_io_PrintStream','printStackTrace$java_io_PrintWriter'], function (stream) { - printStackTrace(this, stream.println$O); + printStackTrace(this, stream); }); Clazz.newMeth(C$, 'fillInStackTrace$', function () { @@ -18896,7 +19152,7 @@ TypeError.prototype.getMessage$ || (TypeError.prototype.getMessage$ = TypeError. = function(){ return (this.stack ? this.stack : this.message || this.toString()) + (this.getStackTrace ? this.getStackTrace$() : Clazz._getStackTrace())}); TypeError.prototype.printStackTrace$ = function(){System.out.println(this + "\n" + this.stack)}; TypeError.prototype.printStackTrace$java_io_PrintStream = function(stream){stream.println$S(this + "\n" + this.stack);}; - +TypeError.prototype.printStackTrace$java_io_PrintWriter = function(printer){printer.println$S(this + "\n" + this.stack);}; Clazz.Error = Error; var declareType = function(prefix, name, clazzSuper, interfacez) { @@ -18911,7 +19167,7 @@ Clazz._Error || (Clazz._Error = Error); //setSuperclass(Clazz._Error, Throwable); var setEx = function(C$) { - C$.$clinit$ = function() {Clazz.load(C$, 1)}; + C$.$clinit$ = 1; m$(C$, "c$", function() { C$.superclazz.c$.apply(this, []);}, 1); m$(C$, "c$$S", function(detailMessage){C$.superclazz.c$$S.apply(this,[detailMessage]);},1); m$(C$, "c$$Throwable", function(exception){C$.superclazz.c$$Throwable.apply(this, arguments);}, 1); @@ -19127,7 +19383,7 @@ var C$=Clazz.newClass(java.util,"MissingResourceException",function(){ this.className=null; this.key=null; },RuntimeException); -C$.$clinit$ = function() {Clazz.load(C$, 1)}; +C$.$clinit$ = 1; m$(C$, "c$$S$S$S", function(detailMessage,className,resourceName){ Clazz.super_(C$, this); C$.superclazz.c$$S.apply(this,[detailMessage]); @@ -19150,71 +19406,6 @@ setJ2STypeclass(java.lang.Void, "void", "V"); //java.lang.V Clazz.newInterface(java.lang.reflect,"GenericDeclaration"); -Clazz.newInterface(java.lang.reflect,"AnnotatedElement"); - -;(function() { -var C$=declareType(java.lang.reflect,"AccessibleObject",null,java.lang.reflect.AnnotatedElement); -m$(C$, "c$",function(){ -}, 1); -m$(C$,"isAccessible$", -function(){ -return true; -}); -m$(C$,"setAccessible$reflect_AccessibleObjectA$Z", -function(objects,flag){ -return; -},1); -m$(C$,"setAccessible$Z", -function(flag){ -return; -}); -m$(C$,"isAnnotationPresent$Class", -function(annotationType){ -return false; -}); -m$(C$,"getDeclaredAnnotations$", -function(){ -return new Array(0); -}); -m$(C$,"getAnnotations$", -function(){ -return new Array(0); -}); -m$(C$,"getAnnotation$Class", -function(annotationType){ -return null; -}); -m$(C$,"marshallArguments$ClassA$OA", -function(parameterTypes,args){ -return null; -}, 1); -m$(C$,"invokeV$O$OA", -function(receiver,args){ -return; -}); -m$(C$,"invokeL$O$OA", -function(receiver,args){ -return null; -}); -m$(C$,"invokeI$O$OA", -function(receiver,args){ -return 0; -}); -m$(C$,"invokeJ$O$OA", -function(receiver,args){ -return 0; -}); -m$(C$,"invokeF$O$OA", -function(receiver,args){ -return 0.0; -}); -m$(C$,"invokeD$O$OA", -function(receiver,args){ -return 0.0; -}); -C$.emptyArgs=C$.prototype.emptyArgs=new Array(0); -})(); - Clazz.newInterface(java.lang.reflect,"InvocationHandler"); diff --git a/sources/net.sf.j2s.java.core/template.html b/sources/net.sf.j2s.java.core/template.html index 4ec9c044f..7c20d4420 100644 --- a/sources/net.sf.j2s.java.core/template.html +++ b/sources/net.sf.j2s.java.core/template.html @@ -1,31 +1,32 @@ - - - -SwingJS test _NAME_ - - - - - - - - - + + + +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/sources/net.sf.j2s.java.core/test-raf.txt b/sources/net.sf.j2s.java.core/test-raf.txt new file mode 100644 index 000000000..163504823 --- /dev/null +++ b/sources/net.sf.j2s.java.core/test-raf.txt @@ -0,0 +1 @@ +this is a testing RandomAccessFile diff --git a/sources/net.sf.j2s.java.core/test-raf1.txt b/sources/net.sf.j2s.java.core/test-raf1.txt new file mode 100644 index 000000000..e69de29bb diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/Log.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/Log.java new file mode 100644 index 000000000..5c5523d5a --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/Log.java @@ -0,0 +1,216 @@ +/* + * 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.commons.logging; + +/** + * A simple logging interface abstracting logging APIs. In order to be + * instantiated successfully by {@link LogFactory}, classes that implement + * this interface must have a constructor that takes a single String + * parameter representing the "name" of this Log. + *

+ * The six logging levels used by Log are (in order): + *

    + *
  1. trace (the least serious)
  2. + *
  3. debug
  4. + *
  5. info
  6. + *
  7. warn
  8. + *
  9. error
  10. + *
  11. fatal (the most serious)
  12. + *
+ * The mapping of these log levels to the concepts used by the underlying + * logging system is implementation dependent. + * The implementation should ensure, though, that this ordering behaves + * as expected. + *

+ * Performance is often a logging concern. + * By examining the appropriate property, + * a component can avoid expensive operations (producing information + * to be logged). + *

+ * For example, + *

+ *    if (log.isDebugEnabled()) {
+ *        ... do something expensive ...
+ *        log.debug(theResult);
+ *    }
+ * 
+ *

+ * Configuration of the underlying logging system will generally be done + * external to the Logging APIs, through whatever mechanism is supported by + * that system. + * + * @version $Id: Log.java 1606045 2014-06-27 12:11:56Z tn $ + */ +public interface Log { + + /** + * Logs a message with debug log level. + * + * @param message log this message + */ + void debug(Object message); + + /** + * Logs an error with debug log level. + * + * @param message log this message + * @param t log this cause + */ + void debug(Object message, Throwable t); + + /** + * Logs a message with error log level. + * + * @param message log this message + */ + void error(Object message); + + /** + * Logs an error with error log level. + * + * @param message log this message + * @param t log this cause + */ + void error(Object message, Throwable t); + + /** + * Logs a message with fatal log level. + * + * @param message log this message + */ + void fatal(Object message); + + /** + * Logs an error with fatal log level. + * + * @param message log this message + * @param t log this cause + */ + void fatal(Object message, Throwable t); + + /** + * Logs a message with info log level. + * + * @param message log this message + */ + void info(Object message); + + /** + * Logs an error with info log level. + * + * @param message log this message + * @param t log this cause + */ + void info(Object message, Throwable t); + + /** + * Is debug logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than debug. + * + * @return true if debug is enabled in the underlying logger. + */ + boolean isDebugEnabled(); + + /** + * Is error logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than error. + * + * @return true if error is enabled in the underlying logger. + */ + boolean isErrorEnabled(); + + /** + * Is fatal logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than fatal. + * + * @return true if fatal is enabled in the underlying logger. + */ + boolean isFatalEnabled(); + + /** + * Is info logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than info. + * + * @return true if info is enabled in the underlying logger. + */ + boolean isInfoEnabled(); + + /** + * Is trace logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than trace. + * + * @return true if trace is enabled in the underlying logger. + */ + boolean isTraceEnabled(); + + /** + * Is warn logging currently enabled? + *

+ * Call this method to prevent having to perform expensive operations + * (for example, String concatenation) + * when the log level is more than warn. + * + * @return true if warn is enabled in the underlying logger. + */ + boolean isWarnEnabled(); + + /** + * Logs a message with trace log level. + * + * @param message log this message + */ + void trace(Object message); + + /** + * Logs an error with trace log level. + * + * @param message log this message + * @param t log this cause + */ + void trace(Object message, Throwable t); + + /** + * Logs a message with warn log level. + * + * @param message log this message + */ + void warn(Object message); + + /** + * Logs an error with warn log level. + * + * @param message log this message + * @param t log this cause + */ + void warn(Object message, Throwable t); +} diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogConfigurationException.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogConfigurationException.java new file mode 100644 index 000000000..e612e9b77 --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogConfigurationException.java @@ -0,0 +1,80 @@ +/* + * 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.commons.logging; + +/** + * An exception that is thrown only if a suitable LogFactory + * or Log instance cannot be created by the corresponding + * factory methods. + * + * @version $Id: LogConfigurationException.java 1432663 2013-01-13 17:24:18Z tn $ + */ +public class LogConfigurationException extends RuntimeException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 8486587136871052495L; + + /** + * Construct a new exception with null as its detail message. + */ + public LogConfigurationException() { + super(); + } + + /** + * Construct a new exception with the specified detail message. + * + * @param message The detail message + */ + public LogConfigurationException(String message) { + super(message); + } + + /** + * Construct a new exception with the specified cause and a derived + * detail message. + * + * @param cause The underlying cause + */ + public LogConfigurationException(Throwable cause) { + this(cause == null ? null : cause.toString(), cause); + } + + /** + * Construct a new exception with the specified detail message and cause. + * + * @param message The detail message + * @param cause The underlying cause + */ + public LogConfigurationException(String message, Throwable cause) { + super(message + " (Caused by " + cause + ")"); + this.cause = cause; // Two-argument version requires JDK 1.4 or later + } + + /** + * The underlying cause of this exception. + */ + protected Throwable cause = null; + + /** + * Return the underlying cause of this exception (if any). + */ + public Throwable getCause() { + return this.cause; + } +} diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogFactory.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogFactory.java new file mode 100644 index 000000000..705cb9675 --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogFactory.java @@ -0,0 +1,1703 @@ +/* + * 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.commons.logging; + +import java.io.BufferedReader; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.net.URL; +import java.net.URLConnection; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; + +/** + * Factory for creating {@link Log} instances, with discovery and + * configuration features similar to that employed by standard Java APIs + * such as JAXP. + *

+ * IMPLEMENTATION NOTE - This implementation is heavily + * based on the SAXParserFactory and DocumentBuilderFactory implementations + * (corresponding to the JAXP pluggability APIs) found in Apache Xerces. + * + * @version $Id: LogFactory.java 1606041 2014-06-27 11:56:59Z tn $ + */ +public abstract class LogFactory { + // Implementation note re AccessController usage + // + // It is important to keep code invoked via an AccessController to small + // auditable blocks. Such code must carefully evaluate all user input + // (parameters, system properties, config file contents, etc). As an + // example, a Log implementation should not write to its logfile + // with an AccessController anywhere in the call stack, otherwise an + // insecure application could configure the log implementation to write + // to a protected file using the privileges granted to JCL rather than + // to the calling application. + // + // Under no circumstance should a non-private method return data that is + // retrieved via an AccessController. That would allow an insecure app + // to invoke that method and obtain data that it is not permitted to have. + // + // Invoking user-supplied code with an AccessController set is not a major + // issue (eg invoking the constructor of the class specified by + // HASHTABLE_IMPLEMENTATION_PROPERTY). That class will be in a different + // trust domain, and therefore must have permissions to do whatever it + // is trying to do regardless of the permissions granted to JCL. There is + // a slight issue in that untrusted code may point that environment var + // to another trusted library, in which case the code runs if both that + // lib and JCL have the necessary permissions even when the untrusted + // caller does not. That's a pretty hard route to exploit though. + + // ----------------------------------------------------- Manifest Constants + + /** + * The name (priority) of the key in the config file used to + * specify the priority of that particular config file. The associated value + * is a floating-point number; higher values take priority over lower values. + */ + public static final String PRIORITY_KEY = "priority"; + + /** + * The name (use_tccl) of the key in the config file used + * to specify whether logging classes should be loaded via the thread + * context class loader (TCCL), or not. By default, the TCCL is used. + */ + public static final String TCCL_KEY = "use_tccl"; + + /** + * The name (org.apache.commons.logging.LogFactory) of the property + * used to identify the LogFactory implementation + * class name. This can be used as a system property, or as an entry in a + * configuration properties file. + */ + public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory"; + + /** + * The fully qualified class name of the fallback LogFactory + * implementation class to use, if no other can be found. + */ + public static final String FACTORY_DEFAULT = "org.apache.commons.logging.impl.LogFactoryImpl"; + + /** + * The name (commons-logging.properties) of the properties file to search for. + */ + public static final String FACTORY_PROPERTIES = "commons-logging.properties"; + + /** + * JDK1.3+ + * 'Service Provider' specification. + */ + protected static final String SERVICE_ID = + "META-INF/services/org.apache.commons.logging.LogFactory"; + + /** + * The name (org.apache.commons.logging.diagnostics.dest) + * of the property used to enable internal commons-logging + * diagnostic output, in order to get information on what logging + * implementations are being discovered, what classloaders they + * are loaded through, etc. + *

+ * If a system property of this name is set then the value is + * assumed to be the name of a file. The special strings + * STDOUT or STDERR (case-sensitive) indicate output to + * System.out and System.err respectively. + *

+ * Diagnostic logging should be used only to debug problematic + * configurations and should not be set in normal production use. + */ + public static final String DIAGNOSTICS_DEST_PROPERTY = + "org.apache.commons.logging.diagnostics.dest"; + + /** + * When null (the usual case), no diagnostic output will be + * generated by LogFactory or LogFactoryImpl. When non-null, + * interesting events will be written to the specified object. + */ + private static PrintStream diagnosticsStream = null; + + /** + * A string that gets prefixed to every message output by the + * logDiagnostic method, so that users can clearly see which + * LogFactory class is generating the output. + */ + private static final String diagnosticPrefix; + + /** + * Setting this system property + * (org.apache.commons.logging.LogFactory.HashtableImpl) + * value allows the Hashtable used to store + * classloaders to be substituted by an alternative implementation. + *

+ * Note: LogFactory will print: + *

+     * [ERROR] LogFactory: Load of custom hashtable failed
+     * 
+ * to system error and then continue using a standard Hashtable. + *

+ * Usage: Set this property when Java is invoked + * and LogFactory will attempt to load a new instance + * of the given implementation class. + * For example, running the following ant scriplet: + *

+     *  <java classname="${test.runner}" fork="yes" failonerror="${test.failonerror}">
+     *     ...
+     *     <sysproperty
+     *        key="org.apache.commons.logging.LogFactory.HashtableImpl"
+     *        value="org.apache.commons.logging.AltHashtable"/>
+     *  </java>
+     * 
+ * will mean that LogFactory will load an instance of + * org.apache.commons.logging.AltHashtable. + *

+ * A typical use case is to allow a custom + * Hashtable implementation using weak references to be substituted. + * This will allow classloaders to be garbage collected without + * the need to release them (on 1.3+ JVMs only, of course ;). + */ + public static final String HASHTABLE_IMPLEMENTATION_PROPERTY = + "org.apache.commons.logging.LogFactory.HashtableImpl"; + + /** Name used to load the weak hashtable implementation by names. */ + private static final String WEAK_HASHTABLE_CLASSNAME = + "org.apache.commons.logging.impl.WeakHashtable"; + + /** + * A reference to the classloader that loaded this class. This is the + * same as LogFactory.class.getClassLoader(). However computing this + * value isn't quite as simple as that, as we potentially need to use + * AccessControllers etc. It's more efficient to compute it once and + * cache it here. + */ + private static final ClassLoader thisClassLoader; + + // ----------------------------------------------------------- Constructors + + /** + * Protected constructor that is not available for public use. + */ + protected LogFactory() { + } + + // --------------------------------------------------------- Public Methods + + /** + * Return the configuration attribute with the specified name (if any), + * or null if there is no such attribute. + * + * @param name Name of the attribute to return + */ + public abstract Object getAttribute(String name); + + /** + * Return an array containing the names of all currently defined + * configuration attributes. If there are no such attributes, a zero + * length array is returned. + */ + public abstract String[] getAttributeNames(); + + /** + * Convenience method to derive a name from the specified class and + * call getInstance(String) with it. + * + * @param clazz Class for which a suitable Log name will be derived + * @throws LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public abstract Log getInstance(Class clazz) + throws LogConfigurationException; + + /** + * Construct (if necessary) and return a Log instance, + * using the factory's current set of configuration attributes. + *

+ * NOTE - Depending upon the implementation of + * the LogFactory you are using, the Log + * instance you are returned may or may not be local to the current + * application, and may or may not be returned again on a subsequent + * call with the same name argument. + * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * @throws LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public abstract Log getInstance(String name) + throws LogConfigurationException; + + /** + * Release any internal references to previously created {@link Log} + * instances returned by this factory. This is useful in environments + * like servlet containers, which implement application reloading by + * throwing away a ClassLoader. Dangling references to objects in that + * class loader would prevent garbage collection. + */ + public abstract void release(); + + /** + * Remove any configuration attribute associated with the specified name. + * If there is no such attribute, no action is taken. + * + * @param name Name of the attribute to remove + */ + public abstract void removeAttribute(String name); + + /** + * Set the configuration attribute with the specified name. Calling + * this with a null value is equivalent to calling + * removeAttribute(name). + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or null + * to remove any setting for this attribute + */ + public abstract void setAttribute(String name, Object value); + + // ------------------------------------------------------- Static Variables + + /** + * The previously constructed LogFactory instances, keyed by + * the ClassLoader with which it was created. + */ + protected static Hashtable factories = null; + + /** + * Previously constructed LogFactory instance as in the + * factories map, but for the case where + * getClassLoader returns null. + * This can happen when: + *

    + *
  • using JDK1.1 and the calling code is loaded via the system + * classloader (very common)
  • + *
  • using JDK1.2+ and the calling code is loaded via the boot + * classloader (only likely for embedded systems work).
  • + *
+ * Note that factories is a Hashtable (not a HashMap), + * and hashtables don't allow null as a key. + * @deprecated since 1.1.2 + */ + protected static volatile LogFactory nullClassLoaderFactory = null; + + /** + * Create the hashtable which will be used to store a map of + * (context-classloader -> logfactory-object). Version 1.2+ of Java + * supports "weak references", allowing a custom Hashtable class + * to be used which uses only weak references to its keys. Using weak + * references can fix memory leaks on webapp unload in some cases (though + * not all). Version 1.1 of Java does not support weak references, so we + * must dynamically determine which we are using. And just for fun, this + * code also supports the ability for a system property to specify an + * arbitrary Hashtable implementation name. + *

+ * Note that the correct way to ensure no memory leaks occur is to ensure + * that LogFactory.release(contextClassLoader) is called whenever a + * webapp is undeployed. + */ + private static final Hashtable createFactoryStore() { + Hashtable result = null; + String storeImplementationClass; + try { + storeImplementationClass = getSystemProperty(HASHTABLE_IMPLEMENTATION_PROPERTY, null); + } catch (SecurityException ex) { + // Permissions don't allow this to be accessed. Default to the "modern" + // weak hashtable implementation if it is available. + storeImplementationClass = null; + } + + if (storeImplementationClass == null) { + storeImplementationClass = WEAK_HASHTABLE_CLASSNAME; + } + try { + Class implementationClass = Class.forName(storeImplementationClass); + result = (Hashtable) implementationClass.newInstance(); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + + // ignore + if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) { + // if the user's trying to set up a custom implementation, give a clue + if (isDiagnosticsEnabled()) { + // use internal logging to issue the warning + logDiagnostic("[ERROR] LogFactory: Load of custom hashtable failed"); + } else { + // we *really* want this output, even if diagnostics weren't + // explicitly enabled by the user. + System.err.println("[ERROR] LogFactory: Load of custom hashtable failed"); + } + } + } + if (result == null) { + result = new Hashtable(); + } + return result; + } + + // --------------------------------------------------------- Static Methods + + /** Utility method to safely trim a string. */ + private static String trim(String src) { + if (src == null) { + return null; + } + return src.trim(); + } + + /** + * Checks whether the supplied Throwable is one that needs to be + * re-thrown and ignores all others. + * + * The following errors are re-thrown: + *

    + *
  • ThreadDeath
  • + *
  • VirtualMachineError
  • + *
+ * + * @param t the Throwable to check + */ + protected static void handleThrowable(Throwable t) { + if (t instanceof ThreadDeath) { + throw (ThreadDeath) t; + } + if (t instanceof VirtualMachineError) { + throw (VirtualMachineError) t; + } + // All other instances of Throwable will be silently ignored + } + + /** + * Construct (if necessary) and return a LogFactory + * instance, using the following ordered lookup procedure to determine + * the name of the implementation class to be loaded. + *

+ *

    + *
  • The org.apache.commons.logging.LogFactory system + * property.
  • + *
  • The JDK 1.3 Service Discovery mechanism
  • + *
  • Use the properties file commons-logging.properties + * file, if found in the class path of this class. The configuration + * file is in standard java.util.Properties format and + * contains the fully qualified name of the implementation class + * with the key being the system property defined above.
  • + *
  • Fall back to a default implementation class + * (org.apache.commons.logging.impl.LogFactoryImpl).
  • + *
+ *

+ * NOTE - If the properties file method of identifying the + * LogFactory implementation class is utilized, all of the + * properties defined in this file will be set as configuration attributes + * on the corresponding LogFactory instance. + *

+ * NOTE - In a multi-threaded environment it is possible + * that two different instances will be returned for the same + * classloader environment. + * + * @throws LogConfigurationException if the implementation class is not + * available or cannot be instantiated. + */ + public static LogFactory getFactory() throws LogConfigurationException { + // Identify the class loader we will be using + ClassLoader contextClassLoader = getContextClassLoaderInternal(); + + if (contextClassLoader == null) { + // This is an odd enough situation to report about. This + // output will be a nuisance on JDK1.1, as the system + // classloader is null in that environment. + if (isDiagnosticsEnabled()) { + logDiagnostic("Context classloader is null."); + } + } + + // Return any previously registered factory for this class loader + LogFactory factory = getCachedFactory(contextClassLoader); + if (factory != null) { + return factory; + } + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] LogFactory implementation requested for the first time for context classloader " + + objectId(contextClassLoader)); + logHierarchy("[LOOKUP] ", contextClassLoader); + } + + // Load properties file. + // + // If the properties file exists, then its contents are used as + // "attributes" on the LogFactory implementation class. One particular + // property may also control which LogFactory concrete subclass is + // used, but only if other discovery mechanisms fail.. + // + // As the properties file (if it exists) will be used one way or + // another in the end we may as well look for it first. + + Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES); + + // Determine whether we will be using the thread context class loader to + // load logging classes or not by checking the loaded properties file (if any). + ClassLoader baseClassLoader = contextClassLoader; + if (props != null) { + String useTCCLStr = props.getProperty(TCCL_KEY); + if (useTCCLStr != null) { + // The Boolean.valueOf(useTCCLStr).booleanValue() formulation + // is required for Java 1.2 compatibility. + if (Boolean.valueOf(useTCCLStr).booleanValue() == false) { + // Don't use current context classloader when locating any + // LogFactory or Log classes, just use the class that loaded + // this abstract class. When this class is deployed in a shared + // classpath of a container, it means webapps cannot deploy their + // own logging implementations. It also means that it is up to the + // implementation whether to load library-specific config files + // from the TCCL or not. + baseClassLoader = thisClassLoader; + } + } + } + + // Determine which concrete LogFactory subclass to use. + // First, try a global system property + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY + + "] to define the LogFactory subclass to use..."); + } + + try { + String factoryClass = getSystemProperty(FACTORY_PROPERTY, null); + if (factoryClass != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass + + "' as specified by system property " + FACTORY_PROPERTY); + } + factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined."); + } + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" + + " instance of the custom factory class" + ": [" + trim(e.getMessage()) + + "]. Trying alternative implementations..."); + } + // ignore + } catch (RuntimeException e) { + // This is not consistent with the behaviour when a bad LogFactory class is + // specified in a services file. + // + // One possible exception that can occur here is a ClassCastException when + // the specified class wasn't castable to this LogFactory type. + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] An exception occurred while trying to create an" + + " instance of the custom factory class" + ": [" + + trim(e.getMessage()) + + "] as specified by a system property."); + } + throw e; + } + + // Second, try to find a service by using the JDK1.3 class + // discovery mechanism, which involves putting a file with the name + // of an interface class in the META-INF/services directory, where the + // contents of the file is a single line specifying a concrete class + // that implements the desired interface. + + if (factory == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID + + "] to define the LogFactory subclass to use..."); + } + try { + final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID); + + if( is != null ) { + // This code is needed by EBCDIC and other strange systems. + // It's a fix for bugs reported in xerces + BufferedReader rd; + try { + rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); + } catch (java.io.UnsupportedEncodingException e) { + rd = new BufferedReader(new InputStreamReader(is)); + } + + String factoryClassName = rd.readLine(); + rd.close(); + + if (factoryClassName != null && ! "".equals(factoryClassName)) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + + factoryClassName + + " as specified by file '" + SERVICE_ID + + "' which was present in the path of the context classloader."); + } + factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader ); + } + } else { + // is == null + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found."); + } + } + } catch (Exception ex) { + // note: if the specified LogFactory class wasn't compatible with LogFactory + // for some reason, a ClassCastException will be caught here, and attempts will + // continue to find a compatible class. + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] A security exception occurred while trying to create an" + + " instance of the custom factory class" + + ": [" + trim(ex.getMessage()) + + "]. Trying alternative implementations..."); + } + // ignore + } + } + + // Third try looking into the properties file read earlier (if found) + + if (factory == null) { + if (props != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY + + "' to define the LogFactory subclass to use..."); + } + String factoryClass = props.getProperty(FACTORY_PROPERTY); + if (factoryClass != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'"); + } + factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); + + // TODO: think about whether we need to handle exceptions from newFactory + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass."); + } + } + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from.."); + } + } + } + + // Fourth, try the fallback implementation class + + if (factory == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT + + "' via the same classloader that loaded this LogFactory" + + " class (ie not looking in the context classloader)."); + } + + // Note: unlike the above code which can try to load custom LogFactory + // implementations via the TCCL, we don't try to load the default LogFactory + // implementation via the context classloader because: + // * that can cause problems (see comments in newFactory method) + // * no-one should be customising the code of the default class + // Yes, we do give up the ability for the child to ship a newer + // version of the LogFactoryImpl class and have it used dynamically + // by an old LogFactory class in the parent, but that isn't + // necessarily a good idea anyway. + factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader); + } + + if (factory != null) { + /** + * Always cache using context class loader. + */ + cacheFactory(contextClassLoader, factory); + + if (props != null) { + Enumeration names = props.propertyNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + String value = props.getProperty(name); + factory.setAttribute(name, value); + } + } + } + + return factory; + } + + /** + * Convenience method to return a named logger, without the application + * having to care about factories. + * + * @param clazz Class from which a log name will be derived + * @throws LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public static Log getLog(Class clazz) throws LogConfigurationException { + return getFactory().getInstance(clazz); + } + + /** + * Convenience method to return a named logger, without the application + * having to care about factories. + * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * @throws LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public static Log getLog(String name) throws LogConfigurationException { + return getFactory().getInstance(name); + } + + /** + * Release any internal references to previously created {@link LogFactory} + * instances that have been associated with the specified class loader + * (if any), after calling the instance method release() on + * each of them. + * + * @param classLoader ClassLoader for which to release the LogFactory + */ + public static void release(ClassLoader classLoader) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Releasing factory for classloader " + objectId(classLoader)); + } + // factories is not final and could be replaced in this block. + final Hashtable factories = LogFactory.factories; + synchronized (factories) { + if (classLoader == null) { + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } else { + final LogFactory factory = (LogFactory) factories.get(classLoader); + if (factory != null) { + factory.release(); + factories.remove(classLoader); + } + } + } + } + + /** + * Release any internal references to previously created {@link LogFactory} + * instances, after calling the instance method release() on + * each of them. This is useful in environments like servlet containers, + * which implement application reloading by throwing away a ClassLoader. + * Dangling references to objects in that class loader would prevent + * garbage collection. + */ + public static void releaseAll() { + if (isDiagnosticsEnabled()) { + logDiagnostic("Releasing factory for all classloaders."); + } + // factories is not final and could be replaced in this block. + final Hashtable factories = LogFactory.factories; + synchronized (factories) { + final Enumeration elements = factories.elements(); + while (elements.hasMoreElements()) { + LogFactory element = (LogFactory) elements.nextElement(); + element.release(); + } + factories.clear(); + + if (nullClassLoaderFactory != null) { + nullClassLoaderFactory.release(); + nullClassLoaderFactory = null; + } + } + } + + // ------------------------------------------------------ Protected Methods + + /** + * Safely get access to the classloader for the specified class. + *

+ * Theoretically, calling getClassLoader can throw a security exception, + * and so should be done under an AccessController in order to provide + * maximum flexibility. However in practice people don't appear to use + * security policies that forbid getClassLoader calls. So for the moment + * all code is written to call this method rather than Class.getClassLoader, + * so that we could put AccessController stuff in this method without any + * disruption later if we need to. + *

+ * Even when using an AccessController, however, this method can still + * throw SecurityException. Commons-logging basically relies on the + * ability to access classloaders, ie a policy that forbids all + * classloader access will also prevent commons-logging from working: + * currently this method will throw an exception preventing the entire app + * from starting up. Maybe it would be good to detect this situation and + * just disable all commons-logging? Not high priority though - as stated + * above, security policies that prevent classloader access aren't common. + *

+ * Note that returning an object fetched via an AccessController would + * technically be a security flaw anyway; untrusted code that has access + * to a trusted JCL library could use it to fetch the classloader for + * a class even when forbidden to do so directly. + * + * @since 1.1 + */ + protected static ClassLoader getClassLoader(Class clazz) { + try { + return clazz.getClassLoader(); + } catch (SecurityException ex) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to get classloader for class '" + clazz + + "' due to security restrictions - " + ex.getMessage()); + } + throw ex; + } + } + + /** + * Returns the current context classloader. + *

+ * In versions prior to 1.1, this method did not use an AccessController. + * In version 1.1, an AccessController wrapper was incorrectly added to + * this method, causing a minor security flaw. + *

+ * In version 1.1.1 this change was reverted; this method no longer uses + * an AccessController. User code wishing to obtain the context classloader + * must invoke this method via AccessController.doPrivileged if it needs + * support for that. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + */ + protected static ClassLoader getContextClassLoader() throws LogConfigurationException { + return directGetContextClassLoader(); + } + + /** + * Calls LogFactory.directGetContextClassLoader under the control of an + * AccessController class. This means that java code running under a + * security manager that forbids access to ClassLoaders will still work + * if this class is given appropriate privileges, even when the caller + * doesn't have such privileges. Without using an AccessController, the + * the entire call stack must have the privilege before the call is + * allowed. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + */ + private static ClassLoader getContextClassLoaderInternal() throws LogConfigurationException { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return directGetContextClassLoader(); + } + }); + } + + /** + * Return the thread context class loader if available; otherwise return null. + *

+ * Most/all code should call getContextClassLoaderInternal rather than + * calling this method directly. + *

+ * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + *

+ * Note that no internal logging is done within this method because + * this method is called every time LogFactory.getLogger() is called, + * and we don't want too much output generated here. + * + * @throws LogConfigurationException if a suitable class loader + * cannot be identified. + * @return the thread's context classloader or {@code null} if the java security + * policy forbids access to the context classloader from one of the classes + * in the current call stack. + * @since 1.1 + */ + protected static ClassLoader directGetContextClassLoader() throws LogConfigurationException { + ClassLoader classLoader = null; + + try { + classLoader = Thread.currentThread().getContextClassLoader(); + } catch (SecurityException ex) { + /** + * getContextClassLoader() throws SecurityException when + * the context class loader isn't an ancestor of the + * calling class's class loader, or if security + * permissions are restricted. + * + * We ignore this exception to be consistent with the previous + * behavior (e.g. 1.1.3 and earlier). + */ + // ignore + } + + // Return the selected class loader + return classLoader; + } + + /** + * Check cached factories (keyed by contextClassLoader) + * + * @param contextClassLoader is the context classloader associated + * with the current thread. This allows separate LogFactory objects + * per component within a container, provided each component has + * a distinct context classloader set. This parameter may be null + * in JDK1.1, and in embedded systems where jcl-using code is + * placed in the bootclasspath. + * + * @return the factory associated with the specified classloader if + * one has previously been created, or null if this is the first time + * we have seen this particular classloader. + */ + private static LogFactory getCachedFactory(ClassLoader contextClassLoader) { + if (contextClassLoader == null) { + // We have to handle this specially, as factories is a Hashtable + // and those don't accept null as a key value. + // + // nb: nullClassLoaderFactory might be null. That's ok. + return nullClassLoaderFactory; + } else { + return (LogFactory) factories.get(contextClassLoader); + } + } + + /** + * Remember this factory, so later calls to LogFactory.getCachedFactory + * can return the previously created object (together with all its + * cached Log objects). + * + * @param classLoader should be the current context classloader. Note that + * this can be null under some circumstances; this is ok. + * @param factory should be the factory to cache. This should never be null. + */ + private static void cacheFactory(ClassLoader classLoader, LogFactory factory) { + // Ideally we would assert(factory != null) here. However reporting + // errors from within a logging implementation is a little tricky! + + if (factory != null) { + if (classLoader == null) { + nullClassLoaderFactory = factory; + } else { + factories.put(classLoader, factory); + } + } + } + + /** + * Return a new instance of the specified LogFactory + * implementation class, loaded by the specified class loader. + * If that fails, try the class loader used to load this + * (abstract) LogFactory. + *

ClassLoader conflicts

+ *

+ * Note that there can be problems if the specified ClassLoader is not the + * same as the classloader that loaded this class, ie when loading a + * concrete LogFactory subclass via a context classloader. + *

+ * The problem is the same one that can occur when loading a concrete Log + * subclass via a context classloader. + *

+ * The problem occurs when code running in the context classloader calls + * class X which was loaded via a parent classloader, and class X then calls + * LogFactory.getFactory (either directly or via LogFactory.getLog). Because + * class X was loaded via the parent, it binds to LogFactory loaded via + * the parent. When the code in this method finds some LogFactoryYYYY + * class in the child (context) classloader, and there also happens to be a + * LogFactory class defined in the child classloader, then LogFactoryYYYY + * will be bound to LogFactory@childloader. It cannot be cast to + * LogFactory@parentloader, ie this method cannot return the object as + * the desired type. Note that it doesn't matter if the LogFactory class + * in the child classloader is identical to the LogFactory class in the + * parent classloader, they are not compatible. + *

+ * The solution taken here is to simply print out an error message when + * this occurs then throw an exception. The deployer of the application + * must ensure they remove all occurrences of the LogFactory class from + * the child classloader in order to resolve the issue. Note that they + * do not have to move the custom LogFactory subclass; that is ok as + * long as the only LogFactory class it can find to bind to is in the + * parent classloader. + * + * @param factoryClass Fully qualified name of the LogFactory + * implementation class + * @param classLoader ClassLoader from which to load this class + * @param contextClassLoader is the context that this new factory will + * manage logging for. + * @throws LogConfigurationException if a suitable instance + * cannot be created + * @since 1.1 + */ + protected static LogFactory newFactory(final String factoryClass, + final ClassLoader classLoader, + final ClassLoader contextClassLoader) + throws LogConfigurationException { + // Note that any unchecked exceptions thrown by the createFactory + // method will propagate out of this method; in particular a + // ClassCastException can be thrown. + Object result = AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return createFactory(factoryClass, classLoader); + } + }); + + if (result instanceof LogConfigurationException) { + LogConfigurationException ex = (LogConfigurationException) result; + if (isDiagnosticsEnabled()) { + logDiagnostic("An error occurred while loading the factory class:" + ex.getMessage()); + } + throw ex; + } + if (isDiagnosticsEnabled()) { + logDiagnostic("Created object " + objectId(result) + " to manage classloader " + + objectId(contextClassLoader)); + } + return (LogFactory)result; + } + + /** + * Method provided for backwards compatibility; see newFactory version that + * takes 3 parameters. + *

+ * This method would only ever be called in some rather odd situation. + * Note that this method is static, so overriding in a subclass doesn't + * have any effect unless this method is called from a method in that + * subclass. However this method only makes sense to use from the + * getFactory method, and as that is almost always invoked via + * LogFactory.getFactory, any custom definition in a subclass would be + * pointless. Only a class with a custom getFactory method, then invoked + * directly via CustomFactoryImpl.getFactory or similar would ever call + * this. Anyway, it's here just in case, though the "managed class loader" + * value output to the diagnostics will not report the correct value. + */ + protected static LogFactory newFactory(final String factoryClass, + final ClassLoader classLoader) { + return newFactory(factoryClass, classLoader, null); + } + + /** + * Implements the operations described in the javadoc for newFactory. + * + * @param factoryClass + * @param classLoader used to load the specified factory class. This is + * expected to be either the TCCL or the classloader which loaded this + * class. Note that the classloader which loaded this class might be + * "null" (ie the bootloader) for embedded systems. + * @return either a LogFactory object or a LogConfigurationException object. + * @since 1.1 + */ + protected static Object createFactory(String factoryClass, ClassLoader classLoader) { + // This will be used to diagnose bad configurations + // and allow a useful message to be sent to the user + Class logFactoryClass = null; + try { + if (classLoader != null) { + try { + // First the given class loader param (thread class loader) + + // Warning: must typecast here & allow exception + // to be generated/caught & recast properly. + logFactoryClass = classLoader.loadClass(factoryClass); + if (LogFactory.class.isAssignableFrom(logFactoryClass)) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Loaded class " + logFactoryClass.getName() + + " from classloader " + objectId(classLoader)); + } + } else { + // + // This indicates a problem with the ClassLoader tree. + // An incompatible ClassLoader was used to load the + // implementation. + // As the same classes + // must be available in multiple class loaders, + // it is very likely that multiple JCL jars are present. + // The most likely fix for this + // problem is to remove the extra JCL jars from the + // ClassLoader hierarchy. + // + if (isDiagnosticsEnabled()) { + logDiagnostic("Factory class " + logFactoryClass.getName() + + " loaded from classloader " + objectId(logFactoryClass.getClassLoader()) + + " does not extend '" + LogFactory.class.getName() + + "' as loaded by this classloader."); + logHierarchy("[BAD CL TREE] ", classLoader); + } + } + + return (LogFactory) logFactoryClass.newInstance(); + + } catch (ClassNotFoundException ex) { + if (classLoader == thisClassLoader) { + // Nothing more to try, onwards. + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to locate any class called '" + factoryClass + + "' via classloader " + objectId(classLoader)); + } + throw ex; + } + // ignore exception, continue + } catch (NoClassDefFoundError e) { + if (classLoader == thisClassLoader) { + // Nothing more to try, onwards. + if (isDiagnosticsEnabled()) { + logDiagnostic("Class '" + factoryClass + "' cannot be loaded" + + " via classloader " + objectId(classLoader) + + " - it depends on some other class that cannot be found."); + } + throw e; + } + // ignore exception, continue + } catch (ClassCastException e) { + if (classLoader == thisClassLoader) { + // There's no point in falling through to the code below that + // tries again with thisClassLoader, because we've just tried + // loading with that loader (not the TCCL). Just throw an + // appropriate exception here. + + final boolean implementsLogFactory = implementsLogFactory(logFactoryClass); + + // + // Construct a good message: users may not actual expect that a custom implementation + // has been specified. Several well known containers use this mechanism to adapt JCL + // to their native logging system. + // + final StringBuffer msg = new StringBuffer(); + msg.append("The application has specified that a custom LogFactory implementation "); + msg.append("should be used but Class '"); + msg.append(factoryClass); + msg.append("' cannot be converted to '"); + msg.append(LogFactory.class.getName()); + msg.append("'. "); + if (implementsLogFactory) { + msg.append("The conflict is caused by the presence of multiple LogFactory classes "); + msg.append("in incompatible classloaders. "); + msg.append("Background can be found in http://commons.apache.org/logging/tech.html. "); + msg.append("If you have not explicitly specified a custom LogFactory then it is likely "); + msg.append("that the container has set one without your knowledge. "); + msg.append("In this case, consider using the commons-logging-adapters.jar file or "); + msg.append("specifying the standard LogFactory from the command line. "); + } else { + msg.append("Please check the custom implementation. "); + } + msg.append("Help can be found @http://commons.apache.org/logging/troubleshooting.html."); + + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + + throw new ClassCastException(msg.toString()); + } + + // Ignore exception, continue. Presumably the classloader was the + // TCCL; the code below will try to load the class via thisClassLoader. + // This will handle the case where the original calling class is in + // a shared classpath but the TCCL has a copy of LogFactory and the + // specified LogFactory implementation; we will fall back to using the + // LogFactory implementation from the same classloader as this class. + // + // Issue: this doesn't handle the reverse case, where this LogFactory + // is in the webapp, and the specified LogFactory implementation is + // in a shared classpath. In that case: + // (a) the class really does implement LogFactory (bad log msg above) + // (b) the fallback code will result in exactly the same problem. + } + } + + /* At this point, either classLoader == null, OR + * classLoader was unable to load factoryClass. + * + * In either case, we call Class.forName, which is equivalent + * to LogFactory.class.getClassLoader().load(name), ie we ignore + * the classloader parameter the caller passed, and fall back + * to trying the classloader associated with this class. See the + * javadoc for the newFactory method for more info on the + * consequences of this. + * + * Notes: + * * LogFactory.class.getClassLoader() may return 'null' + * if LogFactory is loaded by the bootstrap classloader. + */ + // Warning: must typecast here & allow exception + // to be generated/caught & recast properly. + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to load factory class via classloader " + objectId(classLoader) + + " - trying the classloader associated with this LogFactory."); + } + logFactoryClass = Class.forName(factoryClass); + return (LogFactory) logFactoryClass.newInstance(); + } catch (Exception e) { + // Check to see if we've got a bad configuration + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to create LogFactory instance."); + } + if (logFactoryClass != null && !LogFactory.class.isAssignableFrom(logFactoryClass)) { + return new LogConfigurationException( + "The chosen LogFactory implementation does not extend LogFactory." + + " Please check your configuration.", e); + } + return new LogConfigurationException(e); + } + } + + /** + * Determines whether the given class actually implements LogFactory. + * Diagnostic information is also logged. + *

+ * Usage: to diagnose whether a classloader conflict is the cause + * of incompatibility. The test used is whether the class is assignable from + * the LogFactory class loaded by the class's classloader. + * @param logFactoryClass Class which may implement LogFactory + * @return true if the logFactoryClass does extend + * LogFactory when that class is loaded via the same + * classloader that loaded the logFactoryClass. + */ + private static boolean implementsLogFactory(Class logFactoryClass) { + boolean implementsLogFactory = false; + if (logFactoryClass != null) { + try { + ClassLoader logFactoryClassLoader = logFactoryClass.getClassLoader(); + if (logFactoryClassLoader == null) { + logDiagnostic("[CUSTOM LOG FACTORY] was loaded by the boot classloader"); + } else { + logHierarchy("[CUSTOM LOG FACTORY] ", logFactoryClassLoader); + Class factoryFromCustomLoader + = Class.forName("org.apache.commons.logging.LogFactory", false, logFactoryClassLoader); + implementsLogFactory = factoryFromCustomLoader.isAssignableFrom(logFactoryClass); + if (implementsLogFactory) { + logDiagnostic("[CUSTOM LOG FACTORY] " + logFactoryClass.getName() + + " implements LogFactory but was loaded by an incompatible classloader."); + } else { + logDiagnostic("[CUSTOM LOG FACTORY] " + logFactoryClass.getName() + + " does not implement LogFactory."); + } + } + } catch (SecurityException e) { + // + // The application is running within a hostile security environment. + // This will make it very hard to diagnose issues with JCL. + // Consider running less securely whilst debugging this issue. + // + logDiagnostic("[CUSTOM LOG FACTORY] SecurityException thrown whilst trying to determine whether " + + "the compatibility was caused by a classloader conflict: " + e.getMessage()); + } catch (LinkageError e) { + // + // This should be an unusual circumstance. + // LinkageError's usually indicate that a dependent class has incompatibly changed. + // Another possibility may be an exception thrown by an initializer. + // Time for a clean rebuild? + // + logDiagnostic("[CUSTOM LOG FACTORY] LinkageError thrown whilst trying to determine whether " + + "the compatibility was caused by a classloader conflict: " + e.getMessage()); + } catch (ClassNotFoundException e) { + // + // LogFactory cannot be loaded by the classloader which loaded the custom factory implementation. + // The custom implementation is not viable until this is corrected. + // Ensure that the JCL jar and the custom class are available from the same classloader. + // Running with diagnostics on should give information about the classloaders used + // to load the custom factory. + // + logDiagnostic("[CUSTOM LOG FACTORY] LogFactory class cannot be loaded by classloader which loaded " + + "the custom LogFactory implementation. Is the custom factory in the right classloader?"); + } + } + return implementsLogFactory; + } + + /** + * Applets may run in an environment where accessing resources of a loader is + * a secure operation, but where the commons-logging library has explicitly + * been granted permission for that operation. In this case, we need to + * run the operation using an AccessController. + */ + private static InputStream getResourceAsStream(final ClassLoader loader, final String name) { + return (InputStream)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + if (loader != null) { + return loader.getResourceAsStream(name); + } else { + return ClassLoader.getSystemResourceAsStream(name); + } + } + }); + } + + /** + * Given a filename, return an enumeration of URLs pointing to + * all the occurrences of that filename in the classpath. + *

+ * This is just like ClassLoader.getResources except that the + * operation is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + *

+ * If no instances are found, an Enumeration is returned whose + * hasMoreElements method returns false (ie an "empty" enumeration). + * If resources could not be listed for some reason, null is returned. + */ + private static Enumeration getResources(final ClassLoader loader, final String name) { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + try { + if (loader != null) { + return loader.getResources(name); + } else { + return ClassLoader.getSystemResources(name); + } + } catch (IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Exception while trying to find configuration file " + + name + ":" + e.getMessage()); + } + return null; + } catch (NoSuchMethodError e) { + // we must be running on a 1.1 JVM which doesn't support + // ClassLoader.getSystemResources; just return null in + // this case. + return null; + } + } + }; + Object result = AccessController.doPrivileged(action); + return (Enumeration) result; + } + + /** + * Given a URL that refers to a .properties file, load that file. + * This is done under an AccessController so that this method will + * succeed when this jarfile is privileged but the caller is not. + * This method must therefore remain private to avoid security issues. + *

+ * {@code Null} is returned if the URL cannot be opened. + */ + private static Properties getProperties(final URL url) { + PrivilegedAction action = + new PrivilegedAction() { + public Object run() { + InputStream stream = null; + try { + // We must ensure that useCaches is set to false, as the + // default behaviour of java is to cache file handles, and + // this "locks" files, preventing hot-redeploy on windows. + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + stream = connection.getInputStream(); + if (stream != null) { + Properties props = new Properties(); + props.load(stream); + stream.close(); + stream = null; + return props; + } + } catch (IOException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to read URL " + url); + } + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + // ignore exception; this should not happen + if (isDiagnosticsEnabled()) { + logDiagnostic("Unable to close stream for URL " + url); + } + } + } + } + + return null; + } + }; + return (Properties) AccessController.doPrivileged(action); + } + + /** + * Locate a user-provided configuration file. + *

+ * The classpath of the specified classLoader (usually the context classloader) + * is searched for properties files of the specified name. If none is found, + * null is returned. If more than one is found, then the file with the greatest + * value for its PRIORITY property is returned. If multiple files have the + * same PRIORITY value then the first in the classpath is returned. + *

+ * This differs from the 1.0.x releases; those always use the first one found. + * However as the priority is a new field, this change is backwards compatible. + *

+ * The purpose of the priority field is to allow a webserver administrator to + * override logging settings in all webapps by placing a commons-logging.properties + * file in a shared classpath location with a priority > 0; this overrides any + * commons-logging.properties files without priorities which are in the + * webapps. Webapps can also use explicit priorities to override a configuration + * file in the shared classpath if needed. + */ + private static final Properties getConfigurationFile(ClassLoader classLoader, String fileName) { + Properties props = null; + double priority = 0.0; + URL propsUrl = null; + try { + Enumeration urls = getResources(classLoader, fileName); + + if (urls == null) { + return null; + } + + while (urls.hasMoreElements()) { + URL url = (URL) urls.nextElement(); + + Properties newProps = getProperties(url); + if (newProps != null) { + if (props == null) { + propsUrl = url; + props = newProps; + String priorityStr = props.getProperty(PRIORITY_KEY); + priority = 0.0; + if (priorityStr != null) { + priority = Double.parseDouble(priorityStr); + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file found at '" + url + "'" + + " with priority " + priority); + } + } else { + String newPriorityStr = newProps.getProperty(PRIORITY_KEY); + double newPriority = 0.0; + if (newPriorityStr != null) { + newPriority = Double.parseDouble(newPriorityStr); + } + + if (newPriority > priority) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file at '" + url + "'" + + " with priority " + newPriority + + " overrides file at '" + propsUrl + "'" + + " with priority " + priority); + } + + propsUrl = url; + props = newProps; + priority = newPriority; + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("[LOOKUP] Properties file at '" + url + "'" + + " with priority " + newPriority + + " does not override file at '" + propsUrl + "'" + + " with priority " + priority); + } + } + } + + } + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("SecurityException thrown while trying to find/read config files."); + } + } + + if (isDiagnosticsEnabled()) { + if (props == null) { + logDiagnostic("[LOOKUP] No properties file of name '" + fileName + "' found."); + } else { + logDiagnostic("[LOOKUP] Properties file of name '" + fileName + "' found at '" + propsUrl + '"'); + } + } + + return props; + } + + /** + * Read the specified system property, using an AccessController so that + * the property can be read if JCL has been granted the appropriate + * security rights even if the calling code has not. + *

+ * Take care not to expose the value returned by this method to the + * calling application in any way; otherwise the calling app can use that + * info to access data that should not be available to it. + */ + private static String getSystemProperty(final String key, final String def) + throws SecurityException { + return (String) AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return System.getProperty(key, def); + } + }); + } + + /** + * Determines whether the user wants internal diagnostic output. If so, + * returns an appropriate writer object. Users can enable diagnostic + * output by setting the system property named {@link #DIAGNOSTICS_DEST_PROPERTY} to + * a filename, or the special values STDOUT or STDERR. + */ + private static PrintStream initDiagnostics() { + String dest; + try { + dest = getSystemProperty(DIAGNOSTICS_DEST_PROPERTY, null); + if (dest == null) { + return null; + } + } catch (SecurityException ex) { + // We must be running in some very secure environment. + // We just have to assume output is not wanted.. + return null; + } + + if (dest.equals("STDOUT")) { + return System.out; + } else if (dest.equals("STDERR")) { + return System.err; + } else { + try { + // open the file in append mode + FileOutputStream fos = new FileOutputStream(dest, true); + return new PrintStream(fos); + } catch (IOException ex) { + // We should report this to the user - but how? + return null; + } + } + } + + /** + * Indicates true if the user has enabled internal logging. + *

+ * By the way, sorry for the incorrect grammar, but calling this method + * areDiagnosticsEnabled just isn't java beans style. + * + * @return true if calls to logDiagnostic will have any effect. + * @since 1.1 + */ + protected static boolean isDiagnosticsEnabled() { + return diagnosticsStream != null; + } + + /** + * Write the specified message to the internal logging destination. + *

+ * Note that this method is private; concrete subclasses of this class + * should not call it because the diagnosticPrefix string this + * method puts in front of all its messages is LogFactory@...., + * while subclasses should put SomeSubClass@... + *

+ * Subclasses should instead compute their own prefix, then call + * logRawDiagnostic. Note that calling isDiagnosticsEnabled is + * fine for subclasses. + *

+ * Note that it is safe to call this method before initDiagnostics + * is called; any output will just be ignored (as isDiagnosticsEnabled + * will return false). + * + * @param msg is the diagnostic message to be output. + */ + private static final void logDiagnostic(String msg) { + if (diagnosticsStream != null) { + diagnosticsStream.print(diagnosticPrefix); + diagnosticsStream.println(msg); + diagnosticsStream.flush(); + } + } + + /** + * Write the specified message to the internal logging destination. + * + * @param msg is the diagnostic message to be output. + * @since 1.1 + */ + protected static final void logRawDiagnostic(String msg) { + if (diagnosticsStream != null) { + diagnosticsStream.println(msg); + diagnosticsStream.flush(); + } + } + + /** + * Generate useful diagnostics regarding the classloader tree for + * the specified class. + *

+ * As an example, if the specified class was loaded via a webapp's + * classloader, then you may get the following output: + *

+     * Class com.acme.Foo was loaded via classloader 11111
+     * ClassLoader tree: 11111 -> 22222 (SYSTEM) -> 33333 -> BOOT
+     * 
+ *

+ * This method returns immediately if isDiagnosticsEnabled() + * returns false. + * + * @param clazz is the class whose classloader + tree are to be + * output. + */ + private static void logClassLoaderEnvironment(Class clazz) { + if (!isDiagnosticsEnabled()) { + return; + } + + try { + // Deliberately use System.getProperty here instead of getSystemProperty; if + // the overall security policy for the calling application forbids access to + // these variables then we do not want to output them to the diagnostic stream. + logDiagnostic("[ENV] Extension directories (java.ext.dir): " + System.getProperty("java.ext.dir")); + logDiagnostic("[ENV] Application classpath (java.class.path): " + System.getProperty("java.class.path")); + } catch (SecurityException ex) { + logDiagnostic("[ENV] Security setting prevent interrogation of system classpaths."); + } + + String className = clazz.getName(); + ClassLoader classLoader; + + try { + classLoader = getClassLoader(clazz); + } catch (SecurityException ex) { + // not much useful diagnostics we can print here! + logDiagnostic("[ENV] Security forbids determining the classloader for " + className); + return; + } + + logDiagnostic("[ENV] Class " + className + " was loaded via classloader " + objectId(classLoader)); + logHierarchy("[ENV] Ancestry of classloader which loaded " + className + " is ", classLoader); + } + + /** + * Logs diagnostic messages about the given classloader + * and it's hierarchy. The prefix is prepended to the message + * and is intended to make it easier to understand the logs. + * @param prefix + * @param classLoader + */ + private static void logHierarchy(String prefix, ClassLoader classLoader) { + if (!isDiagnosticsEnabled()) { + return; + } + ClassLoader systemClassLoader; + if (classLoader != null) { + final String classLoaderString = classLoader.toString(); + logDiagnostic(prefix + objectId(classLoader) + " == '" + classLoaderString + "'"); + } + + try { + systemClassLoader = ClassLoader.getSystemClassLoader(); + } catch (SecurityException ex) { + logDiagnostic(prefix + "Security forbids determining the system classloader."); + return; + } + if (classLoader != null) { + final StringBuffer buf = new StringBuffer(prefix + "ClassLoader tree:"); + for(;;) { + buf.append(objectId(classLoader)); + if (classLoader == systemClassLoader) { + buf.append(" (SYSTEM) "); + } + + try { + classLoader = classLoader.getParent(); + } catch (SecurityException ex) { + buf.append(" --> SECRET"); + break; + } + + buf.append(" --> "); + if (classLoader == null) { + buf.append("BOOT"); + break; + } + } + logDiagnostic(buf.toString()); + } + } + + /** + * Returns a string that uniquely identifies the specified object, including + * its class. + *

+ * The returned string is of form "classname@hashcode", ie is the same as + * the return value of the Object.toString() method, but works even when + * the specified object's class has overidden the toString method. + * + * @param o may be null. + * @return a string of form classname@hashcode, or "null" if param o is null. + * @since 1.1 + */ + public static String objectId(Object o) { + if (o == null) { + return "null"; + } else { + return o.getClass().getName() + "@" + System.identityHashCode(o); + } + } + + // ---------------------------------------------------------------------- + // Static initialiser block to perform initialisation at class load time. + // + // We can't do this in the class constructor, as there are many + // static methods on this class that can be called before any + // LogFactory instances are created, and they depend upon this + // stuff having been set up. + // + // Note that this block must come after any variable declarations used + // by any methods called from this block, as we want any static initialiser + // associated with the variable to run first. If static initialisers for + // variables run after this code, then (a) their value might be needed + // by methods called from here, and (b) they might *override* any value + // computed here! + // + // So the wisest thing to do is just to place this code at the very end + // of the class file. + // ---------------------------------------------------------------------- + + static { + // note: it's safe to call methods before initDiagnostics (though + // diagnostic output gets discarded). + thisClassLoader = getClassLoader(LogFactory.class); + // In order to avoid confusion where multiple instances of JCL are + // being used via different classloaders within the same app, we + // ensure each logged message has a prefix of form + // [LogFactory from classloader OID] + // + // Note that this prefix should be kept consistent with that + // in LogFactoryImpl. However here we don't need to output info + // about the actual *instance* of LogFactory, as all methods that + // output diagnostics from this class are static. + String classLoaderName; + try { + ClassLoader classLoader = thisClassLoader; + if (thisClassLoader == null) { + classLoaderName = "BOOTLOADER"; + } else { + classLoaderName = objectId(classLoader); + } + } catch (SecurityException e) { + classLoaderName = "UNKNOWN"; + } + diagnosticPrefix = "[LogFactory from " + classLoaderName + "] "; + diagnosticsStream = initDiagnostics(); + logClassLoaderEnvironment(LogFactory.class); + factories = createFactoryStore(); + if (isDiagnosticsEnabled()) { + logDiagnostic("BOOTSTRAP COMPLETED"); + } + } +} diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogSource.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogSource.java new file mode 100644 index 000000000..95a12c03c --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/LogSource.java @@ -0,0 +1,219 @@ +/* + * 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.commons.logging; + +import java.lang.reflect.Constructor; +import java.util.Hashtable; + +import org.apache.commons.logging.impl.NoOpLog; + +/** + * Factory for creating {@link Log} instances. Applications should call + * the makeNewLogInstance() method to instantiate new instances + * of the configured {@link Log} implementation class. + *

+ * By default, calling getInstance() will use the following + * algorithm: + *

    + *
  • If Log4J is available, return an instance of + * org.apache.commons.logging.impl.Log4JLogger.
  • + *
  • If JDK 1.4 or later is available, return an instance of + * org.apache.commons.logging.impl.Jdk14Logger.
  • + *
  • Otherwise, return an instance of + * org.apache.commons.logging.impl.NoOpLog.
  • + *
+ *

+ * You can change the default behavior in one of two ways: + *

    + *
  • On the startup command line, set the system property + * org.apache.commons.logging.log to the name of the + * org.apache.commons.logging.Log implementation class + * you want to use.
  • + *
  • At runtime, call LogSource.setLogImplementation().
  • + *
+ * + * @deprecated Use {@link LogFactory} instead - The default factory + * implementation performs exactly the same algorithm as this class did + * + * @version $Id: LogSource.java 1432675 2013-01-13 17:53:30Z tn $ + */ +public class LogSource { + + // ------------------------------------------------------- Class Attributes + + static protected Hashtable logs = new Hashtable(); + + /** Is log4j available (in the current classpath) */ + static protected boolean log4jIsAvailable = false; + + /** Is JDK 1.4 logging available */ + static protected boolean jdk14IsAvailable = false; + + /** Constructor for current log class */ + static protected Constructor logImplctor = null; + + // ----------------------------------------------------- Class Initializers + + static { + + // Is Log4J Available? + try { + log4jIsAvailable = null != Class.forName("org.apache.log4j.Logger"); + } catch (Throwable t) { + log4jIsAvailable = false; + } + + // Is JDK 1.4 Logging Available? + try { + jdk14IsAvailable = null != Class.forName("java.util.logging.Logger") && + null != Class.forName("org.apache.commons.logging.impl.Jdk14Logger"); + } catch (Throwable t) { + jdk14IsAvailable = false; + } + + // Set the default Log implementation + String name = null; + try { + name = System.getProperty("org.apache.commons.logging.log"); + if (name == null) { + name = System.getProperty("org.apache.commons.logging.Log"); + } + } catch (Throwable t) { + } + if (name != null) { + try { + setLogImplementation(name); + } catch (Throwable t) { + try { + setLogImplementation("org.apache.commons.logging.impl.NoOpLog"); + } catch (Throwable u) { + // ignored + } + } + } else { + try { + if (log4jIsAvailable) { + setLogImplementation("org.apache.commons.logging.impl.Log4JLogger"); + } else if (jdk14IsAvailable) { + setLogImplementation("org.apache.commons.logging.impl.Jdk14Logger"); + } else { + setLogImplementation("org.apache.commons.logging.impl.NoOpLog"); + } + } catch (Throwable t) { + try { + setLogImplementation("org.apache.commons.logging.impl.NoOpLog"); + } catch (Throwable u) { + // ignored + } + } + } + + } + + // ------------------------------------------------------------ Constructor + + /** Don't allow others to create instances. */ + private LogSource() { + } + + // ---------------------------------------------------------- Class Methods + + /** + * Set the log implementation/log implementation factory + * by the name of the class. The given class must implement {@link Log}, + * and provide a constructor that takes a single {@link String} argument + * (containing the name of the log). + */ + static public void setLogImplementation(String classname) + throws LinkageError, NoSuchMethodException, SecurityException, ClassNotFoundException { + try { + Class logclass = Class.forName(classname); + Class[] argtypes = new Class[1]; + argtypes[0] = "".getClass(); + logImplctor = logclass.getConstructor(argtypes); + } catch (Throwable t) { + logImplctor = null; + } + } + + /** + * Set the log implementation/log implementation factory by class. + * The given class must implement {@link Log}, and provide a constructor + * that takes a single {@link String} argument (containing the name of the log). + */ + static public void setLogImplementation(Class logclass) + throws LinkageError, ExceptionInInitializerError, NoSuchMethodException, SecurityException { + Class[] argtypes = new Class[1]; + argtypes[0] = "".getClass(); + logImplctor = logclass.getConstructor(argtypes); + } + + /** Get a Log instance by class name. */ + static public Log getInstance(String name) { + Log log = (Log) logs.get(name); + if (null == log) { + log = makeNewLogInstance(name); + logs.put(name, log); + } + return log; + } + + /** Get a Log instance by class. */ + static public Log getInstance(Class clazz) { + return getInstance(clazz.getName()); + } + + /** + * Create a new {@link Log} implementation, based on the given name. + *

+ * The specific {@link Log} implementation returned is determined by the + * value of the org.apache.commons.logging.log property. The value + * of org.apache.commons.logging.log may be set to the fully specified + * name of a class that implements the {@link Log} interface. This class must + * also have a public constructor that takes a single {@link String} argument + * (containing the name of the {@link Log} to be constructed. + *

+ * When org.apache.commons.logging.log is not set, or when no corresponding + * class can be found, this method will return a Log4JLogger if the log4j Logger + * class is available in the {@link LogSource}'s classpath, or a Jdk14Logger if we + * are on a JDK 1.4 or later system, or NoOpLog if neither of the above conditions is true. + * + * @param name the log name (or category) + */ + static public Log makeNewLogInstance(String name) { + Log log; + try { + Object[] args = { name }; + log = (Log) logImplctor.newInstance(args); + } catch (Throwable t) { + log = null; + } + if (null == log) { + log = new NoOpLog(name); + } + return log; + } + + /** + * Returns a {@link String} array containing the names of + * all logs known to me. + */ + static public String[] getLogNames() { + return (String[]) logs.keySet().toArray(new String[logs.size()]); + } +} diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/Log4JLogger.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/Log4JLogger.java new file mode 100644 index 000000000..de3b140dd --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/Log4JLogger.java @@ -0,0 +1,312 @@ +/* + * 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.commons.logging.impl; + +import java.io.Serializable; +import org.apache.commons.logging.Log; +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; +import org.apache.log4j.Level; + +/** + * Implementation of {@link Log} that maps directly to a + * Logger for log4J version 1.2. + *

+ * Initial configuration of the corresponding Logger instances should be done + * in the usual manner, as outlined in the Log4J documentation. + *

+ * The reason this logger is distinct from the 1.3 logger is that in version 1.2 + * of Log4J: + *

    + *
  • class Logger takes Priority parameters not Level parameters. + *
  • class Level extends Priority + *
+ * Log4J1.3 is expected to change Level so it no longer extends Priority, which is + * a non-binary-compatible change. The class generated by compiling this code against + * log4j 1.2 will therefore not run against log4j 1.3. + * + * @version $Id: Log4JLogger.java 1448119 2013-02-20 12:28:04Z tn $ + */ +public class Log4JLogger implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 5160705895411730424L; + + // ------------------------------------------------------------- Attributes + + /** The fully qualified name of the Log4JLogger class. */ + private static final String FQCN = Log4JLogger.class.getName(); + + /** Log to this logger */ + private transient volatile Logger logger = null; + + /** Logger name */ + private final String name; + + private static final Priority traceLevel; + + // ------------------------------------------------------------ + // Static Initializer. + // + // Note that this must come after the static variable declarations + // otherwise initialiser expressions associated with those variables + // will override any settings done here. + // + // Verify that log4j is available, and that it is version 1.2. + // If an ExceptionInInitializerError is generated, then LogFactoryImpl + // will treat that as meaning that the appropriate underlying logging + // library is just not present - if discovery is in progress then + // discovery will continue. + // ------------------------------------------------------------ + + static { + if (!Priority.class.isAssignableFrom(Level.class)) { + // nope, this is log4j 1.3, so force an ExceptionInInitializerError + throw new InstantiationError("Log4J 1.2 not available"); + } + + // Releases of log4j1.2 >= 1.2.12 have Priority.TRACE available, earlier + // versions do not. If TRACE is not available, then we have to map + // calls to Log.trace(...) onto the DEBUG level. + + Priority _traceLevel; + try { + _traceLevel = (Priority) Level.class.getDeclaredField("TRACE").get(null); + } catch(Exception ex) { + // ok, trace not available + _traceLevel = Level.DEBUG; + } + traceLevel = _traceLevel; + } + + // ------------------------------------------------------------ Constructor + + public Log4JLogger() { + name = null; + } + + /** + * Base constructor. + */ + public Log4JLogger(String name) { + this.name = name; + this.logger = getLogger(); + } + + /** + * For use with a log4j factory. + */ + public Log4JLogger(Logger logger) { + if (logger == null) { + throw new IllegalArgumentException( + "Warning - null logger in constructor; possible log4j misconfiguration."); + } + this.name = logger.getName(); + this.logger = logger; + } + + /** + * Logs a message with org.apache.log4j.Priority.TRACE. + * When using a log4j version that does not support the TRACE + * level, the message will be logged at the DEBUG level. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public void trace(Object message) { + getLogger().log(FQCN, traceLevel, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.TRACE. + * When using a log4j version that does not support the TRACE + * level, the message will be logged at the DEBUG level. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public void trace(Object message, Throwable t) { + getLogger().log(FQCN, traceLevel, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.DEBUG. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public void debug(Object message) { + getLogger().log(FQCN, Level.DEBUG, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.DEBUG. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public void debug(Object message, Throwable t) { + getLogger().log(FQCN, Level.DEBUG, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.INFO. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public void info(Object message) { + getLogger().log(FQCN, Level.INFO, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.INFO. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public void info(Object message, Throwable t) { + getLogger().log(FQCN, Level.INFO, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.WARN. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public void warn(Object message) { + getLogger().log(FQCN, Level.WARN, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.WARN. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public void warn(Object message, Throwable t) { + getLogger().log(FQCN, Level.WARN, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.ERROR. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public void error(Object message) { + getLogger().log(FQCN, Level.ERROR, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.ERROR. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public void error(Object message, Throwable t) { + getLogger().log(FQCN, Level.ERROR, message, t); + } + + /** + * Logs a message with org.apache.log4j.Priority.FATAL. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public void fatal(Object message) { + getLogger().log(FQCN, Level.FATAL, message, null); + } + + /** + * Logs a message with org.apache.log4j.Priority.FATAL. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public void fatal(Object message, Throwable t) { + getLogger().log(FQCN, Level.FATAL, message, t); + } + + /** + * Return the native Logger instance we are using. + */ + public Logger getLogger() { + Logger result = logger; + if (result == null) { + synchronized(this) { + result = logger; + if (result == null) { + logger = result = Logger.getLogger(name); + } + } + } + return result; + } + + /** + * Check whether the Log4j Logger used is enabled for DEBUG priority. + */ + public boolean isDebugEnabled() { + return getLogger().isDebugEnabled(); + } + + /** + * Check whether the Log4j Logger used is enabled for ERROR priority. + */ + public boolean isErrorEnabled() { + return getLogger().isEnabledFor(Level.ERROR); + } + + /** + * Check whether the Log4j Logger used is enabled for FATAL priority. + */ + public boolean isFatalEnabled() { + return getLogger().isEnabledFor(Level.FATAL); + } + + /** + * Check whether the Log4j Logger used is enabled for INFO priority. + */ + public boolean isInfoEnabled() { + return getLogger().isInfoEnabled(); + } + + /** + * Check whether the Log4j Logger used is enabled for TRACE priority. + * When using a log4j version that does not support the TRACE level, this call + * will report whether DEBUG is enabled or not. + */ + public boolean isTraceEnabled() { + return getLogger().isEnabledFor(traceLevel); + } + + /** + * Check whether the Log4j Logger used is enabled for WARN priority. + */ + public boolean isWarnEnabled() { + return getLogger().isEnabledFor(Level.WARN); + } +} diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/LogFactoryImpl.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/LogFactoryImpl.java new file mode 100644 index 000000000..7ec3c2788 --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/LogFactoryImpl.java @@ -0,0 +1,1393 @@ +/* + * 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.commons.logging.impl; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Hashtable; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; +import org.apache.commons.logging.LogFactory; + +/** + * Concrete subclass of {@link LogFactory} that implements the + * following algorithm to dynamically select a logging implementation + * class to instantiate a wrapper for: + *
    + *
  • Use a factory configuration attribute named + * org.apache.commons.logging.Log to identify the + * requested implementation class.
  • + *
  • Use the org.apache.commons.logging.Log system property + * to identify the requested implementation class.
  • + *
  • If Log4J is available, return an instance of + * org.apache.commons.logging.impl.Log4JLogger.
  • + *
  • If JDK 1.4 or later is available, return an instance of + * org.apache.commons.logging.impl.Jdk14Logger.
  • + *
  • Otherwise, return an instance of + * org.apache.commons.logging.impl.SimpleLog.
  • + *
+ *

+ * If the selected {@link Log} implementation class has a + * setLogFactory() method that accepts a {@link LogFactory} + * parameter, this method will be called on each newly created instance + * to identify the associated factory. This makes factory configuration + * attributes available to the Log instance, if it so desires. + *

+ * This factory will remember previously created Log instances + * for the same name, and will return them on repeated requests to the + * getInstance() method. + * + * @version $Id: LogFactoryImpl.java 1449064 2013-02-22 14:49:22Z tn $ + */ +public class LogFactoryImpl extends LogFactory { + + /** Log4JLogger class name */ + private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger"; + /** Jdk14Logger class name */ + private static final String LOGGING_IMPL_JDK14_LOGGER = "org.apache.commons.logging.impl.Jdk14Logger"; + /** Jdk13LumberjackLogger class name */ + private static final String LOGGING_IMPL_LUMBERJACK_LOGGER = + "org.apache.commons.logging.impl.Jdk13LumberjackLogger"; + + /** SimpleLog class name */ + private static final String LOGGING_IMPL_SIMPLE_LOGGER = "org.apache.commons.logging.impl.SimpleLog"; + + private static final String PKG_IMPL="org.apache.commons.logging.impl."; + private static final int PKG_LEN = PKG_IMPL.length(); + + // ----------------------------------------------------------- Constructors + + /** + * Public no-arguments constructor required by the lookup mechanism. + */ + public LogFactoryImpl() { + super(); + initDiagnostics(); // method on this object + if (isDiagnosticsEnabled()) { + logDiagnostic("Instance created."); + } + } + + // ----------------------------------------------------- Manifest Constants + + /** + * The name (org.apache.commons.logging.Log) of the system + * property identifying our {@link Log} implementation class. + */ + public static final String LOG_PROPERTY = "org.apache.commons.logging.Log"; + + /** + * The deprecated system property used for backwards compatibility with + * old versions of JCL. + */ + protected static final String LOG_PROPERTY_OLD = "org.apache.commons.logging.log"; + + /** + * The name (org.apache.commons.logging.Log.allowFlawedContext) + * of the system property which can be set true/false to + * determine system behaviour when a bad context-classloader is encountered. + * When set to false, a LogConfigurationException is thrown if + * LogFactoryImpl is loaded via a child classloader of the TCCL (this + * should never happen in sane systems). + * + * Default behaviour: true (tolerates bad context classloaders) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_CONTEXT_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedContext"; + + /** + * The name (org.apache.commons.logging.Log.allowFlawedDiscovery) + * of the system property which can be set true/false to + * determine system behaviour when a bad logging adapter class is + * encountered during logging discovery. When set to false, an + * exception will be thrown and the app will fail to start. When set + * to true, discovery will continue (though the user might end up + * with a different logging implementation than they expected). + *

+ * Default behaviour: true (tolerates bad logging adapters) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_DISCOVERY_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedDiscovery"; + + /** + * The name (org.apache.commons.logging.Log.allowFlawedHierarchy) + * of the system property which can be set true/false to + * determine system behaviour when a logging adapter class is + * encountered which has bound to the wrong Log class implementation. + * When set to false, an exception will be thrown and the app will fail + * to start. When set to true, discovery will continue (though the user + * might end up with a different logging implementation than they expected). + *

+ * Default behaviour: true (tolerates bad Log class hierarchy) + * + * See also method setAttribute. + */ + public static final String ALLOW_FLAWED_HIERARCHY_PROPERTY = + "org.apache.commons.logging.Log.allowFlawedHierarchy"; + + /** + * The names of classes that will be tried (in order) as logging + * adapters. Each class is expected to implement the Log interface, + * and to throw NoClassDefFound or ExceptionInInitializerError when + * loaded if the underlying logging library is not available. Any + * other error indicates that the underlying logging library is available + * but broken/unusable for some reason. + */ + private static final String[] classesToDiscover = { + LOGGING_IMPL_LOG4J_LOGGER, + "org.apache.commons.logging.impl.Jdk14Logger", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger", + "org.apache.commons.logging.impl.SimpleLog" + }; + + // ----------------------------------------------------- Instance Variables + + /** + * Determines whether logging classes should be loaded using the thread-context + * classloader, or via the classloader that loaded this LogFactoryImpl class. + */ + private boolean useTCCL = true; + + /** + * The string prefixed to every message output by the logDiagnostic method. + */ + private String diagnosticPrefix; + + /** + * Configuration attributes. + */ + protected Hashtable attributes = new Hashtable(); + + /** + * The {@link org.apache.commons.logging.Log} instances that have + * already been created, keyed by logger name. + */ + protected Hashtable instances = new Hashtable(); + + /** + * Name of the class implementing the Log interface. + */ + private String logClassName; + + /** + * The one-argument constructor of the + * {@link org.apache.commons.logging.Log} + * implementation class that will be used to create new instances. + * This value is initialized by getLogConstructor(), + * and then returned repeatedly. + */ + protected Constructor logConstructor = null; + + /** + * The signature of the Constructor to be used. + */ + protected Class logConstructorSignature[] = { java.lang.String.class }; + + /** + * The one-argument setLogFactory method of the selected + * {@link org.apache.commons.logging.Log} method, if it exists. + */ + protected Method logMethod = null; + + /** + * The signature of the setLogFactory method to be used. + */ + protected Class logMethodSignature[] = { LogFactory.class }; + + /** + * See getBaseClassLoader and initConfiguration. + */ + private boolean allowFlawedContext; + + /** + * See handleFlawedDiscovery and initConfiguration. + */ + private boolean allowFlawedDiscovery; + + /** + * See handleFlawedHierarchy and initConfiguration. + */ + private boolean allowFlawedHierarchy; + + // --------------------------------------------------------- Public Methods + + /** + * Return the configuration attribute with the specified name (if any), + * or null if there is no such attribute. + * + * @param name Name of the attribute to return + */ + public Object getAttribute(String name) { + return attributes.get(name); + } + + /** + * Return an array containing the names of all currently defined + * configuration attributes. If there are no such attributes, a zero + * length array is returned. + */ + public String[] getAttributeNames() { + return (String[]) attributes.keySet().toArray(new String[attributes.size()]); + } + + /** + * Convenience method to derive a name from the specified class and + * call getInstance(String) with it. + * + * @param clazz Class for which a suitable Log name will be derived + * + * @exception LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public Log getInstance(Class clazz) throws LogConfigurationException { + return getInstance(clazz.getName()); + } + + /** + *

Construct (if necessary) and return a Log instance, + * using the factory's current set of configuration attributes.

+ * + *

NOTE - Depending upon the implementation of + * the LogFactory you are using, the Log + * instance you are returned may or may not be local to the current + * application, and may or may not be returned again on a subsequent + * call with the same name argument.

+ * + * @param name Logical name of the Log instance to be + * returned (the meaning of this name is only known to the underlying + * logging implementation that is being wrapped) + * + * @exception LogConfigurationException if a suitable Log + * instance cannot be returned + */ + public Log getInstance(String name) throws LogConfigurationException { + Log instance = (Log) instances.get(name); + if (instance == null) { + instance = newInstance(name); + instances.put(name, instance); + } + return instance; + } + + /** + * Release any internal references to previously created + * {@link org.apache.commons.logging.Log} + * instances returned by this factory. This is useful in environments + * like servlet containers, which implement application reloading by + * throwing away a ClassLoader. Dangling references to objects in that + * class loader would prevent garbage collection. + */ + public void release() { + + logDiagnostic("Releasing all known loggers"); + instances.clear(); + } + + /** + * Remove any configuration attribute associated with the specified name. + * If there is no such attribute, no action is taken. + * + * @param name Name of the attribute to remove + */ + public void removeAttribute(String name) { + attributes.remove(name); + } + + /** + * Set the configuration attribute with the specified name. Calling + * this with a null value is equivalent to calling + * removeAttribute(name). + *

+ * This method can be used to set logging configuration programmatically + * rather than via system properties. It can also be used in code running + * within a container (such as a webapp) to configure behaviour on a + * per-component level instead of globally as system properties would do. + * To use this method instead of a system property, call + *

+     * LogFactory.getFactory().setAttribute(...)
+     * 
+ * This must be done before the first Log object is created; configuration + * changes after that point will be ignored. + *

+ * This method is also called automatically if LogFactory detects a + * commons-logging.properties file; every entry in that file is set + * automatically as an attribute here. + * + * @param name Name of the attribute to set + * @param value Value of the attribute to set, or null + * to remove any setting for this attribute + */ + public void setAttribute(String name, Object value) { + if (logConstructor != null) { + logDiagnostic("setAttribute: call too late; configuration already performed."); + } + + if (value == null) { + attributes.remove(name); + } else { + attributes.put(name, value); + } + + if (name.equals(TCCL_KEY)) { + useTCCL = value != null && Boolean.valueOf(value.toString()).booleanValue(); + } + } + + // ------------------------------------------------------ + // Static Methods + // + // These methods only defined as workarounds for a java 1.2 bug; + // theoretically none of these are needed. + // ------------------------------------------------------ + + /** + * Gets the context classloader. + * This method is a workaround for a java 1.2 compiler bug. + * @since 1.1 + */ + protected static ClassLoader getContextClassLoader() throws LogConfigurationException { + return LogFactory.getContextClassLoader(); + } + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * See LogFactory.isDiagnosticsEnabled. + */ + protected static boolean isDiagnosticsEnabled() { + return LogFactory.isDiagnosticsEnabled(); + } + + /** + * Workaround for bug in Java1.2; in theory this method is not needed. + * See LogFactory.getClassLoader. + * @since 1.1 + */ + protected static ClassLoader getClassLoader(Class clazz) { + return LogFactory.getClassLoader(clazz); + } + + // ------------------------------------------------------ Protected Methods + + /** + * Calculate and cache a string that uniquely identifies this instance, + * including which classloader the object was loaded from. + *

+ * This string will later be prefixed to each "internal logging" message + * emitted, so that users can clearly see any unexpected behaviour. + *

+ * Note that this method does not detect whether internal logging is + * enabled or not, nor where to output stuff if it is; that is all + * handled by the parent LogFactory class. This method just computes + * its own unique prefix for log messages. + */ + private void initDiagnostics() { + // It would be nice to include an identifier of the context classloader + // that this LogFactoryImpl object is responsible for. However that + // isn't possible as that information isn't available. It is possible + // to figure this out by looking at the logging from LogFactory to + // see the context & impl ids from when this object was instantiated, + // in order to link the impl id output as this object's prefix back to + // the context it is intended to manage. + // Note that this prefix should be kept consistent with that + // in LogFactory. + Class clazz = this.getClass(); + ClassLoader classLoader = getClassLoader(clazz); + String classLoaderName; + try { + if (classLoader == null) { + classLoaderName = "BOOTLOADER"; + } else { + classLoaderName = objectId(classLoader); + } + } catch (SecurityException e) { + classLoaderName = "UNKNOWN"; + } + diagnosticPrefix = "[LogFactoryImpl@" + System.identityHashCode(this) + " from " + classLoaderName + "] "; + } + + /** + * Output a diagnostic message to a user-specified destination (if the + * user has enabled diagnostic logging). + * + * @param msg diagnostic message + * @since 1.1 + */ + protected void logDiagnostic(String msg) { + if (isDiagnosticsEnabled()) { + logRawDiagnostic(diagnosticPrefix + msg); + } + } + + /** + * Return the fully qualified Java classname of the {@link Log} + * implementation we will be using. + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected String getLogClassName() { + if (logClassName == null) { + discoverLogImplementation(getClass().getName()); + } + + return logClassName; + } + + + /** + *

Return the Constructor that can be called to instantiate + * new {@link org.apache.commons.logging.Log} instances.

+ * + *

IMPLEMENTATION NOTE - Race conditions caused by + * calling this method from more than one thread are ignored, because + * the same Constructor instance will ultimately be derived + * in all circumstances.

+ * + * @exception LogConfigurationException if a suitable constructor + * cannot be returned + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected Constructor getLogConstructor() + throws LogConfigurationException { + + // Return the previously identified Constructor (if any) + if (logConstructor == null) { + discoverLogImplementation(getClass().getName()); + } + + return logConstructor; + } + + /** + * Is JDK 1.3 with Lumberjack logging available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk13LumberjackAvailable() { + return isLogLibraryAvailable( + "Jdk13Lumberjack", + "org.apache.commons.logging.impl.Jdk13LumberjackLogger"); + } + + /** + * Return true if JDK 1.4 or later logging + * is available. Also checks that the Throwable class + * supports getStackTrace(), which is required by + * Jdk14Logger. + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isJdk14Available() { + return isLogLibraryAvailable( + "Jdk14", + "org.apache.commons.logging.impl.Jdk14Logger"); + } + + /** + * Is a Log4J implementation available? + * + * @deprecated Never invoked by this class; subclasses should not assume + * it will be. + */ + protected boolean isLog4JAvailable() { + return isLogLibraryAvailable( + "Log4J", + LOGGING_IMPL_LOG4J_LOGGER); + } + + /** + * Create and return a new {@link org.apache.commons.logging.Log} + * instance for the specified name. + * + * @param name Name of the new logger + * + * @exception LogConfigurationException if a new instance cannot + * be created + */ + protected Log newInstance(String name) throws LogConfigurationException { + Log instance; + try { + if (logConstructor == null) { + instance = discoverLogImplementation(name); + } + else { + Object params[] = { name }; + instance = (Log) logConstructor.newInstance(params); + } + + if (logMethod != null) { + Object params[] = { this }; + logMethod.invoke(instance, params); + } + + return instance; + + } catch (LogConfigurationException lce) { + + // this type of exception means there was a problem in discovery + // and we've already output diagnostics about the issue, etc.; + // just pass it on + throw lce; + + } catch (InvocationTargetException e) { + // A problem occurred invoking the Constructor or Method + // previously discovered + Throwable c = e.getTargetException(); + throw new LogConfigurationException(c == null ? e : c); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + // A problem occurred invoking the Constructor or Method + // previously discovered + throw new LogConfigurationException(t); + } + } + + // ------------------------------------------------------ Private Methods + + /** + * Calls LogFactory.directGetContextClassLoader under the control of an + * AccessController class. This means that java code running under a + * security manager that forbids access to ClassLoaders will still work + * if this class is given appropriate privileges, even when the caller + * doesn't have such privileges. Without using an AccessController, the + * the entire call stack must have the privilege before the call is + * allowed. + * + * @return the context classloader associated with the current thread, + * or null if security doesn't allow it. + * + * @throws LogConfigurationException if there was some weird error while + * attempting to get the context classloader. + * + * @throws SecurityException if the current java security policy doesn't + * allow this class to access the context classloader. + */ + private static ClassLoader getContextClassLoaderInternal() + throws LogConfigurationException { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return LogFactory.directGetContextClassLoader(); + } + }); + } + + /** + * Read the specified system property, using an AccessController so that + * the property can be read if JCL has been granted the appropriate + * security rights even if the calling code has not. + *

+ * Take care not to expose the value returned by this method to the + * calling application in any way; otherwise the calling app can use that + * info to access data that should not be available to it. + */ + private static String getSystemProperty(final String key, final String def) + throws SecurityException { + return (String) AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return System.getProperty(key, def); + } + }); + } + + /** + * Fetch the parent classloader of a specified classloader. + *

+ * If a SecurityException occurs, null is returned. + *

+ * Note that this method is non-static merely so logDiagnostic is available. + */ + private ClassLoader getParentClassLoader(final ClassLoader cl) { + try { + return (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return cl.getParent(); + } + }); + } catch (SecurityException ex) { + logDiagnostic("[SECURITY] Unable to obtain parent classloader"); + return null; + } + + } + + /** + * Utility method to check whether a particular logging library is + * present and available for use. Note that this does not + * affect the future behaviour of this class. + */ + private boolean isLogLibraryAvailable(String name, String classname) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Checking for '" + name + "'."); + } + try { + Log log = createLogFromClass( + classname, + this.getClass().getName(), // dummy category + false); + + if (log == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Did not find '" + name + "'."); + } + return false; + } else { + if (isDiagnosticsEnabled()) { + logDiagnostic("Found '" + name + "'."); + } + return true; + } + } catch (LogConfigurationException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Logging system '" + name + "' is available but not useable."); + } + return false; + } + } + + /** + * Attempt to find an attribute (see method setAttribute) or a + * system property with the provided name and return its value. + *

+ * The attributes associated with this object are checked before + * system properties in case someone has explicitly called setAttribute, + * or a configuration property has been set in a commons-logging.properties + * file. + * + * @return the value associated with the property, or null. + */ + private String getConfigurationValue(String property) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Trying to get configuration for item " + property); + } + + Object valueObj = getAttribute(property); + if (valueObj != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Found LogFactory attribute [" + valueObj + "] for " + property); + } + return valueObj.toString(); + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No LogFactory attribute found for " + property); + } + + try { + // warning: minor security hole here, in that we potentially read a system + // property that the caller cannot, then output it in readable form as a + // diagnostic message. However it's only ever JCL-specific properties + // involved here, so the harm is truly trivial. + String value = getSystemProperty(property, null); + if (value != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Found system property [" + value + "] for " + property); + } + return value; + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No system property found for property " + property); + } + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] Security prevented reading system property " + property); + } + } + + if (isDiagnosticsEnabled()) { + logDiagnostic("[ENV] No configuration defined for item " + property); + } + + return null; + } + + /** + * Get the setting for the user-configurable behaviour specified by key. + * If nothing has explicitly been set, then return dflt. + */ + private boolean getBooleanConfiguration(String key, boolean dflt) { + String val = getConfigurationValue(key); + if (val == null) { + return dflt; + } + return Boolean.valueOf(val).booleanValue(); + } + + /** + * Initialize a number of variables that control the behaviour of this + * class and that can be tweaked by the user. This is done when the first + * logger is created, not in the constructor of this class, because we + * need to give the user a chance to call method setAttribute in order to + * configure this object. + */ + private void initConfiguration() { + allowFlawedContext = getBooleanConfiguration(ALLOW_FLAWED_CONTEXT_PROPERTY, true); + allowFlawedDiscovery = getBooleanConfiguration(ALLOW_FLAWED_DISCOVERY_PROPERTY, true); + allowFlawedHierarchy = getBooleanConfiguration(ALLOW_FLAWED_HIERARCHY_PROPERTY, true); + } + + /** + * Attempts to create a Log instance for the given category name. + * Follows the discovery process described in the class javadoc. + * + * @param logCategory the name of the log category + * + * @throws LogConfigurationException if an error in discovery occurs, + * or if no adapter at all can be instantiated + */ + private Log discoverLogImplementation(String logCategory) + throws LogConfigurationException { + if (isDiagnosticsEnabled()) { + logDiagnostic("Discovering a Log implementation..."); + } + + initConfiguration(); + + Log result = null; + + // See if the user specified the Log implementation to use + String specifiedLogClassName = findUserSpecifiedLogClassName(); + + if (specifiedLogClassName != null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Attempting to load user-specified log class '" + + specifiedLogClassName + "'..."); + } + + result = createLogFromClass(specifiedLogClassName, + logCategory, + true); + if (result == null) { + StringBuffer messageBuffer = new StringBuffer("User-specified log class '"); + messageBuffer.append(specifiedLogClassName); + messageBuffer.append("' cannot be found or is not useable."); + + // Mistyping or misspelling names is a common fault. + // Construct a good error message, if we can + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER); + informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER); + throw new LogConfigurationException(messageBuffer.toString()); + } + + return result; + } + + // No user specified log; try to discover what's on the classpath + // + // Note that we deliberately loop here over classesToDiscover and + // expect method createLogFromClass to loop over the possible source + // classloaders. The effect is: + // for each discoverable log adapter + // for each possible classloader + // see if it works + // + // It appears reasonable at first glance to do the opposite: + // for each possible classloader + // for each discoverable log adapter + // see if it works + // + // The latter certainly has advantages for user-installable logging + // libraries such as log4j; in a webapp for example this code should + // first check whether the user has provided any of the possible + // logging libraries before looking in the parent classloader. + // Unfortunately, however, Jdk14Logger will always work in jvm>=1.4, + // and SimpleLog will always work in any JVM. So the loop would never + // ever look for logging libraries in the parent classpath. Yet many + // users would expect that putting log4j there would cause it to be + // detected (and this is the historical JCL behaviour). So we go with + // the first approach. A user that has bundled a specific logging lib + // in a webapp should use a commons-logging.properties file or a + // service file in META-INF to force use of that logging lib anyway, + // rather than relying on discovery. + + if (isDiagnosticsEnabled()) { + logDiagnostic( + "No user-specified Log implementation; performing discovery" + + " using the standard supported logging implementations..."); + } + for(int i=0; iStringBuffer the message should be appended to, + * not null + * @param name the (trimmed) name to be test against the candidate, not null + * @param candidate the candidate name (not null) + */ + private void informUponSimilarName(final StringBuffer messageBuffer, final String name, + final String candidate) { + if (name.equals(candidate)) { + // Don't suggest a name that is exactly the same as the one the + // user tried... + return; + } + + // If the user provides a name that is in the right package, and gets + // the first 5 characters of the adapter class right (ignoring case), + // then suggest the candidate adapter class name. + if (name.regionMatches(true, 0, candidate, 0, PKG_LEN + 5)) { + messageBuffer.append(" Did you mean '"); + messageBuffer.append(candidate); + messageBuffer.append("'?"); + } + } + + /** + * Checks system properties and the attribute map for + * a Log implementation specified by the user under the + * property names {@link #LOG_PROPERTY} or {@link #LOG_PROPERTY_OLD}. + * + * @return classname specified by the user, or null + */ + private String findUserSpecifiedLogClassName() { + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from attribute '" + LOG_PROPERTY + "'"); + } + String specifiedClass = (String) getAttribute(LOG_PROPERTY); + + if (specifiedClass == null) { // @deprecated + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from attribute '" + + LOG_PROPERTY_OLD + "'"); + } + specifiedClass = (String) getAttribute(LOG_PROPERTY_OLD); + } + + if (specifiedClass == null) { + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from system property '" + + LOG_PROPERTY + "'"); + } + try { + specifiedClass = getSystemProperty(LOG_PROPERTY, null); + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("No access allowed to system property '" + + LOG_PROPERTY + "' - " + e.getMessage()); + } + } + } + + if (specifiedClass == null) { // @deprecated + if (isDiagnosticsEnabled()) { + logDiagnostic("Trying to get log class from system property '" + + LOG_PROPERTY_OLD + "'"); + } + try { + specifiedClass = getSystemProperty(LOG_PROPERTY_OLD, null); + } catch (SecurityException e) { + if (isDiagnosticsEnabled()) { + logDiagnostic("No access allowed to system property '" + + LOG_PROPERTY_OLD + "' - " + e.getMessage()); + } + } + } + + // Remove any whitespace; it's never valid in a classname so its + // presence just means a user mistake. As we know what they meant, + // we may as well strip the spaces. + if (specifiedClass != null) { + specifiedClass = specifiedClass.trim(); + } + + return specifiedClass; + } + + /** + * Attempts to load the given class, find a suitable constructor, + * and instantiate an instance of Log. + * + * @param logAdapterClassName classname of the Log implementation + * @param logCategory argument to pass to the Log implementation's constructor + * @param affectState true if this object's state should + * be affected by this method call, false otherwise. + * @return an instance of the given class, or null if the logging + * library associated with the specified adapter is not available. + * @throws LogConfigurationException if there was a serious error with + * configuration and the handleFlawedDiscovery method decided this + * problem was fatal. + */ + private Log createLogFromClass(String logAdapterClassName, + String logCategory, + boolean affectState) + throws LogConfigurationException { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Attempting to instantiate '" + logAdapterClassName + "'"); + } + + Object[] params = { logCategory }; + Log logAdapter = null; + Constructor constructor = null; + + Class logAdapterClass = null; + ClassLoader currentCL = getBaseClassLoader(); + + for(;;) { + // Loop through the classloader hierarchy trying to find + // a viable classloader. + logDiagnostic("Trying to load '" + logAdapterClassName + "' from classloader " + objectId(currentCL)); + try { + if (isDiagnosticsEnabled()) { + // Show the location of the first occurrence of the .class file + // in the classpath. This is the location that ClassLoader.loadClass + // will load the class from -- unless the classloader is doing + // something weird. + URL url; + String resourceName = logAdapterClassName.replace('.', '/') + ".class"; + if (currentCL != null) { + url = currentCL.getResource(resourceName ); + } else { + url = ClassLoader.getSystemResource(resourceName + ".class"); + } + + if (url == null) { + logDiagnostic("Class '" + logAdapterClassName + "' [" + resourceName + "] cannot be found."); + } else { + logDiagnostic("Class '" + logAdapterClassName + "' was found at '" + url + "'"); + } + } + + Class c; + try { + c = Class.forName(logAdapterClassName, true, currentCL); + } catch (ClassNotFoundException originalClassNotFoundException) { + // The current classloader was unable to find the log adapter + // in this or any ancestor classloader. There's no point in + // trying higher up in the hierarchy in this case.. + String msg = originalClassNotFoundException.getMessage(); + logDiagnostic("The log adapter '" + logAdapterClassName + "' is not available via classloader " + + objectId(currentCL) + ": " + msg.trim()); + try { + // Try the class classloader. + // This may work in cases where the TCCL + // does not contain the code executed or JCL. + // This behaviour indicates that the application + // classloading strategy is not consistent with the + // Java 1.2 classloading guidelines but JCL can + // and so should handle this case. + c = Class.forName(logAdapterClassName); + } catch (ClassNotFoundException secondaryClassNotFoundException) { + // no point continuing: this adapter isn't available + msg = secondaryClassNotFoundException.getMessage(); + logDiagnostic("The log adapter '" + logAdapterClassName + + "' is not available via the LogFactoryImpl class classloader: " + msg.trim()); + break; + } + } + + constructor = c.getConstructor(logConstructorSignature); + Object o = constructor.newInstance(params); + + // Note that we do this test after trying to create an instance + // [rather than testing Log.class.isAssignableFrom(c)] so that + // we don't complain about Log hierarchy problems when the + // adapter couldn't be instantiated anyway. + if (o instanceof Log) { + logAdapterClass = c; + logAdapter = (Log) o; + break; + } + + // Oops, we have a potential problem here. An adapter class + // has been found and its underlying lib is present too, but + // there are multiple Log interface classes available making it + // impossible to cast to the type the caller wanted. We + // certainly can't use this logger, but we need to know whether + // to keep on discovering or terminate now. + // + // The handleFlawedHierarchy method will throw + // LogConfigurationException if it regards this problem as + // fatal, and just return if not. + handleFlawedHierarchy(currentCL, c); + } catch (NoClassDefFoundError e) { + // We were able to load the adapter but it had references to + // other classes that could not be found. This simply means that + // the underlying logger library is not present in this or any + // ancestor classloader. There's no point in trying higher up + // in the hierarchy in this case.. + String msg = e.getMessage(); + logDiagnostic("The log adapter '" + logAdapterClassName + + "' is missing dependencies when loaded via classloader " + objectId(currentCL) + + ": " + msg.trim()); + break; + } catch (ExceptionInInitializerError e) { + // A static initializer block or the initializer code associated + // with a static variable on the log adapter class has thrown + // an exception. + // + // We treat this as meaning the adapter's underlying logging + // library could not be found. + String msg = e.getMessage(); + logDiagnostic("The log adapter '" + logAdapterClassName + + "' is unable to initialize itself when loaded via classloader " + objectId(currentCL) + + ": " + msg.trim()); + break; + } catch (LogConfigurationException e) { + // call to handleFlawedHierarchy above must have thrown + // a LogConfigurationException, so just throw it on + throw e; + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + // handleFlawedDiscovery will determine whether this is a fatal + // problem or not. If it is fatal, then a LogConfigurationException + // will be thrown. + handleFlawedDiscovery(logAdapterClassName, currentCL, t); + } + + if (currentCL == null) { + break; + } + + // try the parent classloader + // currentCL = currentCL.getParent(); + currentCL = getParentClassLoader(currentCL); + } + + if (logAdapterClass != null && affectState) { + // We've succeeded, so set instance fields + this.logClassName = logAdapterClassName; + this.logConstructor = constructor; + + // Identify the setLogFactory method (if there is one) + try { + this.logMethod = logAdapterClass.getMethod("setLogFactory", logMethodSignature); + logDiagnostic("Found method setLogFactory(LogFactory) in '" + logAdapterClassName + "'"); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + this.logMethod = null; + logDiagnostic("[INFO] '" + logAdapterClassName + "' from classloader " + objectId(currentCL) + + " does not declare optional method " + "setLogFactory(LogFactory)"); + } + + logDiagnostic("Log adapter '" + logAdapterClassName + "' from classloader " + + objectId(logAdapterClass.getClassLoader()) + " has been selected for use."); + } + + return logAdapter; + } + + /** + * Return the classloader from which we should try to load the logging + * adapter classes. + *

+ * This method usually returns the context classloader. However if it + * is discovered that the classloader which loaded this class is a child + * of the context classloader and the allowFlawedContext option + * has been set then the classloader which loaded this class is returned + * instead. + *

+ * The only time when the classloader which loaded this class is a + * descendant (rather than the same as or an ancestor of the context + * classloader) is when an app has created custom classloaders but + * failed to correctly set the context classloader. This is a bug in + * the calling application; however we provide the option for JCL to + * simply generate a warning rather than fail outright. + * + */ + private ClassLoader getBaseClassLoader() throws LogConfigurationException { + ClassLoader thisClassLoader = getClassLoader(LogFactoryImpl.class); + + if (!useTCCL) { + return thisClassLoader; + } + + ClassLoader contextClassLoader = getContextClassLoaderInternal(); + + ClassLoader baseClassLoader = getLowestClassLoader( + contextClassLoader, thisClassLoader); + + if (baseClassLoader == null) { + // The two classloaders are not part of a parent child relationship. + // In some classloading setups (e.g. JBoss with its + // UnifiedLoaderRepository) this can still work, so if user hasn't + // forbidden it, just return the contextClassLoader. + if (allowFlawedContext) { + if (isDiagnosticsEnabled()) { + logDiagnostic("[WARNING] the context classloader is not part of a" + + " parent-child relationship with the classloader that" + + " loaded LogFactoryImpl."); + } + // If contextClassLoader were null, getLowestClassLoader() would + // have returned thisClassLoader. The fact we are here means + // contextClassLoader is not null, so we can just return it. + return contextClassLoader; + } + else { + throw new LogConfigurationException("Bad classloader hierarchy; LogFactoryImpl was loaded via" + + " a classloader that is not related to the current context" + + " classloader."); + } + } + + if (baseClassLoader != contextClassLoader) { + // We really should just use the contextClassLoader as the starting + // point for scanning for log adapter classes. However it is expected + // that there are a number of broken systems out there which create + // custom classloaders but fail to set the context classloader so + // we handle those flawed systems anyway. + if (allowFlawedContext) { + if (isDiagnosticsEnabled()) { + logDiagnostic( + "Warning: the context classloader is an ancestor of the" + + " classloader that loaded LogFactoryImpl; it should be" + + " the same or a descendant. The application using" + + " commons-logging should ensure the context classloader" + + " is used correctly."); + } + } else { + throw new LogConfigurationException( + "Bad classloader hierarchy; LogFactoryImpl was loaded via" + + " a classloader that is not related to the current context" + + " classloader."); + } + } + + return baseClassLoader; + } + + /** + * Given two related classloaders, return the one which is a child of + * the other. + *

+ * @param c1 is a classloader (including the null classloader) + * @param c2 is a classloader (including the null classloader) + * + * @return c1 if it has c2 as an ancestor, c2 if it has c1 as an ancestor, + * and null if neither is an ancestor of the other. + */ + private ClassLoader getLowestClassLoader(ClassLoader c1, ClassLoader c2) { + // TODO: use AccessController when dealing with classloaders here + + if (c1 == null) { + return c2; + } + + if (c2 == null) { + return c1; + } + + ClassLoader current; + + // scan c1's ancestors to find c2 + current = c1; + while (current != null) { + if (current == c2) { + return c1; + } + // current = current.getParent(); + current = getParentClassLoader(current); + } + + // scan c2's ancestors to find c1 + current = c2; + while (current != null) { + if (current == c1) { + return c2; + } + // current = current.getParent(); + current = getParentClassLoader(current); + } + + return null; + } + + /** + * Generates an internal diagnostic logging of the discovery failure and + * then throws a LogConfigurationException that wraps + * the passed Throwable. + * + * @param logAdapterClassName is the class name of the Log implementation + * that could not be instantiated. Cannot be null. + * + * @param classLoader is the classloader that we were trying to load the + * logAdapterClassName from when the exception occurred. + * + * @param discoveryFlaw is the Throwable created by the classloader + * + * @throws LogConfigurationException ALWAYS + */ + private void handleFlawedDiscovery(String logAdapterClassName, + ClassLoader classLoader, // USED? + Throwable discoveryFlaw) { + + if (isDiagnosticsEnabled()) { + logDiagnostic("Could not instantiate Log '" + + logAdapterClassName + "' -- " + + discoveryFlaw.getClass().getName() + ": " + + discoveryFlaw.getLocalizedMessage()); + + if (discoveryFlaw instanceof InvocationTargetException ) { + // Ok, the lib is there but while trying to create a real underlying + // logger something failed in the underlying lib; display info about + // that if possible. + InvocationTargetException ite = (InvocationTargetException)discoveryFlaw; + Throwable cause = ite.getTargetException(); + if (cause != null) { + logDiagnostic("... InvocationTargetException: " + + cause.getClass().getName() + ": " + + cause.getLocalizedMessage()); + + if (cause instanceof ExceptionInInitializerError) { + ExceptionInInitializerError eiie = (ExceptionInInitializerError)cause; + Throwable cause2 = eiie.getException(); + if (cause2 != null) { + final StringWriter sw = new StringWriter(); + cause2.printStackTrace(new PrintWriter(sw, true)); + logDiagnostic("... ExceptionInInitializerError: " + sw.toString()); + } + } + } + } + } + + if (!allowFlawedDiscovery) { + throw new LogConfigurationException(discoveryFlaw); + } + } + + /** + * Report a problem loading the log adapter, then either return + * (if the situation is considered recoverable) or throw a + * LogConfigurationException. + *

+ * There are two possible reasons why we successfully loaded the + * specified log adapter class then failed to cast it to a Log object: + *

    + *
  1. the specific class just doesn't implement the Log interface + * (user screwed up), or + *
  2. the specified class has bound to a Log class loaded by some other + * classloader; Log@classloaderX cannot be cast to Log@classloaderY. + *
+ *

+ * Here we try to figure out which case has occurred so we can give the + * user some reasonable feedback. + * + * @param badClassLoader is the classloader we loaded the problem class from, + * ie it is equivalent to badClass.getClassLoader(). + * + * @param badClass is a Class object with the desired name, but which + * does not implement Log correctly. + * + * @throws LogConfigurationException when the situation + * should not be recovered from. + */ + private void handleFlawedHierarchy(ClassLoader badClassLoader, Class badClass) + throws LogConfigurationException { + + boolean implementsLog = false; + String logInterfaceName = Log.class.getName(); + Class interfaces[] = badClass.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + if (logInterfaceName.equals(interfaces[i].getName())) { + implementsLog = true; + break; + } + } + + if (implementsLog) { + // the class does implement an interface called Log, but + // it is in the wrong classloader + if (isDiagnosticsEnabled()) { + try { + ClassLoader logInterfaceClassLoader = getClassLoader(Log.class); + logDiagnostic("Class '" + badClass.getName() + "' was found in classloader " + + objectId(badClassLoader) + ". It is bound to a Log interface which is not" + + " the one loaded from classloader " + objectId(logInterfaceClassLoader)); + } catch (Throwable t) { + handleThrowable(t); // may re-throw t + logDiagnostic("Error while trying to output diagnostics about" + " bad class '" + badClass + "'"); + } + } + + if (!allowFlawedHierarchy) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context "); + msg.append("due to bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("Warning: bad log hierarchy. "); + msg.append("You have more than one version of '"); + msg.append(Log.class.getName()); + msg.append("' visible."); + logDiagnostic(msg.toString()); + } + } else { + // this is just a bad adapter class + if (!allowFlawedDiscovery) { + StringBuffer msg = new StringBuffer(); + msg.append("Terminating logging for this context. "); + msg.append("Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + if (isDiagnosticsEnabled()) { + logDiagnostic(msg.toString()); + } + + throw new LogConfigurationException(msg.toString()); + } + + if (isDiagnosticsEnabled()) { + StringBuffer msg = new StringBuffer(); + msg.append("[WARNING] Log class '"); + msg.append(badClass.getName()); + msg.append("' does not implement the Log interface."); + logDiagnostic(msg.toString()); + } + } + } +} diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/NoOpLog.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/NoOpLog.java new file mode 100644 index 000000000..ef0de242e --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/NoOpLog.java @@ -0,0 +1,104 @@ +/* + * 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.commons.logging.impl; + +import java.io.Serializable; +import org.apache.commons.logging.Log; + +/** + * Trivial implementation of Log that throws away all messages. No + * configurable system properties are supported. + * + * @version $Id: NoOpLog.java 1432663 2013-01-13 17:24:18Z tn $ + */ +public class NoOpLog implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 561423906191706148L; + + /** Convenience constructor */ + public NoOpLog() { } + /** Base constructor */ + public NoOpLog(String name) { } + /** Do nothing */ + public void trace(Object message) { } + /** Do nothing */ + public void trace(Object message, Throwable t) { } + /** Do nothing */ + public void debug(Object message) { } + /** Do nothing */ + public void debug(Object message, Throwable t) { } + /** Do nothing */ + public void info(Object message) { } + /** Do nothing */ + public void info(Object message, Throwable t) { } + /** Do nothing */ + public void warn(Object message) { } + /** Do nothing */ + public void warn(Object message, Throwable t) { } + /** Do nothing */ + public void error(Object message) { } + /** Do nothing */ + public void error(Object message, Throwable t) { } + /** Do nothing */ + public void fatal(Object message) { } + /** Do nothing */ + public void fatal(Object message, Throwable t) { } + + /** + * Debug is never enabled. + * + * @return false + */ + public final boolean isDebugEnabled() { return false; } + + /** + * Error is never enabled. + * + * @return false + */ + public final boolean isErrorEnabled() { return false; } + + /** + * Fatal is never enabled. + * + * @return false + */ + public final boolean isFatalEnabled() { return false; } + + /** + * Info is never enabled. + * + * @return false + */ + public final boolean isInfoEnabled() { return false; } + + /** + * Trace is never enabled. + * + * @return false + */ + public final boolean isTraceEnabled() { return false; } + + /** + * Warn is never enabled. + * + * @return false + */ + public final boolean isWarnEnabled() { return false; } +} diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/SimpleLog.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/SimpleLog.java new file mode 100644 index 000000000..d4756afff --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/SimpleLog.java @@ -0,0 +1,649 @@ +/* + * 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.commons.logging.impl; + +import java.io.InputStream; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogConfigurationException; + +/** + * Simple implementation of Log that sends all enabled log messages, + * for all defined loggers, to System.err. The following system properties + * are supported to configure the behavior of this logger: + *

    + *
  • org.apache.commons.logging.simplelog.defaultlog - + * Default logging detail level for all instances of SimpleLog. + * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). + * If not specified, defaults to "info".
  • + *
  • org.apache.commons.logging.simplelog.log.xxxxx - + * Logging detail level for a SimpleLog instance named "xxxxx". + * Must be one of ("trace", "debug", "info", "warn", "error", or "fatal"). + * If not specified, the default logging detail level is used.
  • + *
  • org.apache.commons.logging.simplelog.showlogname - + * Set to true if you want the Log instance name to be + * included in output messages. Defaults to false.
  • + *
  • org.apache.commons.logging.simplelog.showShortLogname - + * Set to true if you want the last component of the name to be + * included in output messages. Defaults to true.
  • + *
  • org.apache.commons.logging.simplelog.showdatetime - + * Set to true if you want the current date and time + * to be included in output messages. Default is false.
  • + *
  • org.apache.commons.logging.simplelog.dateTimeFormat - + * The date and time format to be used in the output messages. + * The pattern describing the date and time format is the same that is + * used in java.text.SimpleDateFormat. If the format is not + * specified or is invalid, the default format is used. + * The default format is yyyy/MM/dd HH:mm:ss:SSS zzz.
  • + *
+ *

+ * In addition to looking for system properties with the names specified + * above, this implementation also checks for a class loader resource named + * "simplelog.properties", and includes any matching definitions + * from this resource (if it exists). + * + * @version $Id: SimpleLog.java 1435115 2013-01-18 12:40:19Z tn $ + */ +public class SimpleLog implements Log, Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 136942970684951178L; + + // ------------------------------------------------------- Class Attributes + + /** All system properties used by SimpleLog start with this */ + static protected final String systemPrefix = "org.apache.commons.logging.simplelog."; + + /** Properties loaded from simplelog.properties */ + static protected final Properties simpleLogProps = new Properties(); + + /** The default format to use when formating dates */ + static protected final String DEFAULT_DATE_TIME_FORMAT = "yyyy/MM/dd HH:mm:ss:SSS zzz"; + + /** Include the instance name in the log message? */ + static volatile protected boolean showLogName = false; + + /** Include the short name ( last component ) of the logger in the log + * message. Defaults to true - otherwise we'll be lost in a flood of + * messages without knowing who sends them. + */ + static volatile protected boolean showShortName = true; + + /** Include the current time in the log message */ + static volatile protected boolean showDateTime = false; + + /** The date and time format to use in the log message */ + static volatile protected String dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + + /** + * Used to format times. + *

+ * Any code that accesses this object should first obtain a lock on it, + * ie use synchronized(dateFormatter); this requirement was introduced + * in 1.1.1 to fix an existing thread safety bug (SimpleDateFormat.format + * is not thread-safe). + */ + static protected DateFormat dateFormatter = null; + + // ---------------------------------------------------- Log Level Constants + + /** "Trace" level logging. */ + public static final int LOG_LEVEL_TRACE = 1; + /** "Debug" level logging. */ + public static final int LOG_LEVEL_DEBUG = 2; + /** "Info" level logging. */ + public static final int LOG_LEVEL_INFO = 3; + /** "Warn" level logging. */ + public static final int LOG_LEVEL_WARN = 4; + /** "Error" level logging. */ + public static final int LOG_LEVEL_ERROR = 5; + /** "Fatal" level logging. */ + public static final int LOG_LEVEL_FATAL = 6; + + /** Enable all logging levels */ + public static final int LOG_LEVEL_ALL = LOG_LEVEL_TRACE - 1; + + /** Enable no logging levels */ + public static final int LOG_LEVEL_OFF = LOG_LEVEL_FATAL + 1; + + // ------------------------------------------------------------ Initializer + + private static String getStringProperty(String name) { + String prop = null; + try { + prop = System.getProperty(name); + } catch (SecurityException e) { + // Ignore + } + return prop == null ? simpleLogProps.getProperty(name) : prop; + } + + private static String getStringProperty(String name, String dephault) { + String prop = getStringProperty(name); + return prop == null ? dephault : prop; + } + + private static boolean getBooleanProperty(String name, boolean dephault) { + String prop = getStringProperty(name); + return prop == null ? dephault : "true".equalsIgnoreCase(prop); + } + + // Initialize class attributes. + // Load properties file, if found. + // Override with system properties. + static { + // Add props from the resource simplelog.properties + InputStream in = getResourceAsStream("simplelog.properties"); + if(null != in) { + try { + simpleLogProps.load(in); + in.close(); + } catch(java.io.IOException e) { + // ignored + } + } + + showLogName = getBooleanProperty(systemPrefix + "showlogname", showLogName); + showShortName = getBooleanProperty(systemPrefix + "showShortLogname", showShortName); + showDateTime = getBooleanProperty(systemPrefix + "showdatetime", showDateTime); + + if(showDateTime) { + dateTimeFormat = getStringProperty(systemPrefix + "dateTimeFormat", + dateTimeFormat); + try { + dateFormatter = new SimpleDateFormat(dateTimeFormat); + } catch(IllegalArgumentException e) { + // If the format pattern is invalid - use the default format + dateTimeFormat = DEFAULT_DATE_TIME_FORMAT; + dateFormatter = new SimpleDateFormat(dateTimeFormat); + } + } + } + + // ------------------------------------------------------------- Attributes + + /** The name of this simple log instance */ + protected volatile String logName = null; + /** The current log level */ + protected volatile int currentLogLevel; + /** The short name of this simple log instance */ + private volatile String shortLogName = null; + + // ------------------------------------------------------------ Constructor + + /** + * Construct a simple log with given name. + * + * @param name log name + */ + public SimpleLog(String name) { + logName = name; + + // Set initial log level + // Used to be: set default log level to ERROR + // IMHO it should be lower, but at least info ( costin ). + setLevel(SimpleLog.LOG_LEVEL_INFO); + + // Set log level from properties + String lvl = getStringProperty(systemPrefix + "log." + logName); + int i = String.valueOf(name).lastIndexOf("."); + while(null == lvl && i > -1) { + name = name.substring(0,i); + lvl = getStringProperty(systemPrefix + "log." + name); + i = String.valueOf(name).lastIndexOf("."); + } + + if(null == lvl) { + lvl = getStringProperty(systemPrefix + "defaultlog"); + } + + if("all".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ALL); + } else if("trace".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_TRACE); + } else if("debug".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_DEBUG); + } else if("info".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_INFO); + } else if("warn".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_WARN); + } else if("error".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_ERROR); + } else if("fatal".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_FATAL); + } else if("off".equalsIgnoreCase(lvl)) { + setLevel(SimpleLog.LOG_LEVEL_OFF); + } + } + + // -------------------------------------------------------- Properties + + /** + * Set logging level. + * + * @param currentLogLevel new logging level + */ + public void setLevel(int currentLogLevel) { + this.currentLogLevel = currentLogLevel; + } + + /** + * Get logging level. + */ + public int getLevel() { + return currentLogLevel; + } + + // -------------------------------------------------------- Logging Methods + + /** + * Do the actual logging. + *

+ * This method assembles the message and then calls write() + * to cause it to be written. + * + * @param type One of the LOG_LEVEL_XXX constants defining the log level + * @param message The message itself (typically a String) + * @param t The exception whose stack trace should be logged + */ + protected void log(int type, Object message, Throwable t) { + // Use a string buffer for better performance + final StringBuffer buf = new StringBuffer(); + + // Append date-time if so configured + if(showDateTime) { + final Date now = new Date(); + String dateText; + synchronized(dateFormatter) { + dateText = dateFormatter.format(now); + } + buf.append(dateText); + buf.append(" "); + } + + // Append a readable representation of the log level + switch(type) { + case SimpleLog.LOG_LEVEL_TRACE: buf.append("[TRACE] "); break; + case SimpleLog.LOG_LEVEL_DEBUG: buf.append("[DEBUG] "); break; + case SimpleLog.LOG_LEVEL_INFO: buf.append("[INFO] "); break; + case SimpleLog.LOG_LEVEL_WARN: buf.append("[WARN] "); break; + case SimpleLog.LOG_LEVEL_ERROR: buf.append("[ERROR] "); break; + case SimpleLog.LOG_LEVEL_FATAL: buf.append("[FATAL] "); break; + } + + // Append the name of the log instance if so configured + if(showShortName) { + if(shortLogName == null) { + // Cut all but the last component of the name for both styles + final String slName = logName.substring(logName.lastIndexOf(".") + 1); + shortLogName = slName.substring(slName.lastIndexOf("/") + 1); + } + buf.append(String.valueOf(shortLogName)).append(" - "); + } else if(showLogName) { + buf.append(String.valueOf(logName)).append(" - "); + } + + // Append the message + buf.append(String.valueOf(message)); + + // Append stack trace if not null + if(t != null) { + buf.append(" <"); + buf.append(t.toString()); + buf.append(">"); + + final java.io.StringWriter sw = new java.io.StringWriter(1024); + final java.io.PrintWriter pw = new java.io.PrintWriter(sw); + t.printStackTrace(pw); + pw.close(); + buf.append(sw.toString()); + } + + // Print to the appropriate destination + write(buf); + } + + /** + * Write the content of the message accumulated in the specified + * StringBuffer to the appropriate output destination. The + * default implementation writes to System.err. + * + * @param buffer A StringBuffer containing the accumulated + * text to be logged + */ + protected void write(StringBuffer buffer) { + System.err.println(buffer.toString()); + } + + /** + * Is the given log level currently enabled? + * + * @param logLevel is this level enabled? + */ + protected boolean isLevelEnabled(int logLevel) { + // log level are numerically ordered so can use simple numeric + // comparison + return logLevel >= currentLogLevel; + } + + // -------------------------------------------------------- Log Implementation + + /** + * Logs a message with + * org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG. + * + * @param message to log + * @see org.apache.commons.logging.Log#debug(Object) + */ + public final void debug(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { + log(SimpleLog.LOG_LEVEL_DEBUG, message, null); + } + } + + /** + * Logs a message with + * org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_DEBUG. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#debug(Object, Throwable) + */ + public final void debug(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG)) { + log(SimpleLog.LOG_LEVEL_DEBUG, message, t); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE. + * + * @param message to log + * @see org.apache.commons.logging.Log#trace(Object) + */ + public final void trace(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { + log(SimpleLog.LOG_LEVEL_TRACE, message, null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_TRACE. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#trace(Object, Throwable) + */ + public final void trace(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE)) { + log(SimpleLog.LOG_LEVEL_TRACE, message, t); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO. + * + * @param message to log + * @see org.apache.commons.logging.Log#info(Object) + */ + public final void info(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { + log(SimpleLog.LOG_LEVEL_INFO,message,null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_INFO. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#info(Object, Throwable) + */ + public final void info(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_INFO)) { + log(SimpleLog.LOG_LEVEL_INFO, message, t); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN. + * + * @param message to log + * @see org.apache.commons.logging.Log#warn(Object) + */ + public final void warn(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { + log(SimpleLog.LOG_LEVEL_WARN, message, null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_WARN. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#warn(Object, Throwable) + */ + public final void warn(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_WARN)) { + log(SimpleLog.LOG_LEVEL_WARN, message, t); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR. + * + * @param message to log + * @see org.apache.commons.logging.Log#error(Object) + */ + public final void error(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { + log(SimpleLog.LOG_LEVEL_ERROR, message, null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_ERROR. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#error(Object, Throwable) + */ + public final void error(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR)) { + log(SimpleLog.LOG_LEVEL_ERROR, message, t); + } + } + + /** + * Log a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL. + * + * @param message to log + * @see org.apache.commons.logging.Log#fatal(Object) + */ + public final void fatal(Object message) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { + log(SimpleLog.LOG_LEVEL_FATAL, message, null); + } + } + + /** + * Logs a message with org.apache.commons.logging.impl.SimpleLog.LOG_LEVEL_FATAL. + * + * @param message to log + * @param t log this cause + * @see org.apache.commons.logging.Log#fatal(Object, Throwable) + */ + public final void fatal(Object message, Throwable t) { + if (isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL)) { + log(SimpleLog.LOG_LEVEL_FATAL, message, t); + } + } + + /** + * Are debug messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isDebugEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_DEBUG); + } + + /** + * Are error messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isErrorEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_ERROR); + } + + /** + * Are fatal messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isFatalEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_FATAL); + } + + /** + * Are info messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isInfoEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_INFO); + } + + /** + * Are trace messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isTraceEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_TRACE); + } + + /** + * Are warn messages currently enabled? + *

+ * This allows expensive operations such as String + * concatenation to be avoided when the message will be ignored by the + * logger. + */ + public final boolean isWarnEnabled() { + return isLevelEnabled(SimpleLog.LOG_LEVEL_WARN); + } + + /** + * Return the thread context class loader if available. + * Otherwise return null. + * + * The thread context class loader is available for JDK 1.2 + * or later, if certain security conditions are met. + * + * @exception LogConfigurationException if a suitable class loader + * cannot be identified. + */ + private static ClassLoader getContextClassLoader() { + ClassLoader classLoader = null; + + try { + // Are we running on a JDK 1.2 or later system? + final Method method = Thread.class.getMethod("getContextClassLoader", (Class[]) null); + + // Get the thread context class loader (if there is one) + try { + classLoader = (ClassLoader)method.invoke(Thread.currentThread(), (Class[]) null); + } catch (IllegalAccessException e) { + // ignore + } catch (InvocationTargetException e) { + /** + * InvocationTargetException is thrown by 'invoke' when + * the method being invoked (getContextClassLoader) throws + * an exception. + * + * getContextClassLoader() throws SecurityException when + * the context class loader isn't an ancestor of the + * calling class's class loader, or if security + * permissions are restricted. + * + * In the first case (not related), we want to ignore and + * keep going. We cannot help but also ignore the second + * with the logic below, but other calls elsewhere (to + * obtain a class loader) will trigger this exception where + * we can make a distinction. + */ + if (e.getTargetException() instanceof SecurityException) { + // ignore + } else { + // Capture 'e.getTargetException()' exception for details + // alternate: log 'e.getTargetException()', and pass back 'e'. + throw new LogConfigurationException + ("Unexpected InvocationTargetException", e.getTargetException()); + } + } + } catch (NoSuchMethodException e) { + // Assume we are running on JDK 1.1 + // ignore + } + + if (classLoader == null) { + classLoader = SimpleLog.class.getClassLoader(); + } + + // Return the selected class loader + return classLoader; + } + + private static InputStream getResourceAsStream(final String name) { + return (InputStream)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + ClassLoader threadCL = getContextClassLoader(); + + if (threadCL != null) { + return threadCL.getResourceAsStream(name); + } else { + return ClassLoader.getSystemResourceAsStream(name); + } + } + }); + } +} + diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/WeakHashtable.java b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/WeakHashtable.java new file mode 100644 index 000000000..c5773f3df --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/WeakHashtable.java @@ -0,0 +1,482 @@ +/* + * 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.commons.logging.impl; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of Hashtable that uses WeakReference's + * to hold its keys thus allowing them to be reclaimed by the garbage collector. + * The associated values are retained using strong references. + *

+ * This class follows the semantics of Hashtable as closely as + * possible. It therefore does not accept null values or keys. + *

+ * Note: + * This is not intended to be a general purpose hash table replacement. + * This implementation is also tuned towards a particular purpose: for use as a replacement + * for Hashtable in LogFactory. This application requires + * good liveliness for get and put. Various tradeoffs + * have been made with this in mind. + *

+ * Usage: typical use case is as a drop-in replacement + * for the Hashtable used in LogFactory for J2EE environments + * running 1.3+ JVMs. Use of this class in most cases (see below) will + * allow classloaders to be collected by the garbage collector without the need + * to call {@link org.apache.commons.logging.LogFactory#release(ClassLoader) LogFactory.release(ClassLoader)}. + *

+ * org.apache.commons.logging.LogFactory checks whether this class + * can be supported by the current JVM, and if so then uses it to store + * references to the LogFactory implementation it loads + * (rather than using a standard Hashtable instance). + * Having this class used instead of Hashtable solves + * certain issues related to dynamic reloading of applications in J2EE-style + * environments. However this class requires java 1.3 or later (due to its use + * of java.lang.ref.WeakReference and associates). + * And by the way, this extends Hashtable rather than HashMap + * for backwards compatibility reasons. See the documentation + * for method LogFactory.createFactoryStore for more details. + *

+ * The reason all this is necessary is due to a issue which + * arises during hot deploy in a J2EE-like containers. + * Each component running in the container owns one or more classloaders; when + * the component loads a LogFactory instance via the component classloader + * a reference to it gets stored in the static LogFactory.factories member, + * keyed by the component's classloader so different components don't + * stomp on each other. When the component is later unloaded, the container + * sets the component's classloader to null with the intent that all the + * component's classes get garbage-collected. However there's still a + * reference to the component's classloader from a key in the "global" + * LogFactory's factories member! If LogFactory.release() + * is called whenever component is unloaded, the classloaders will be correctly + * garbage collected; this should be done by any container that + * bundles commons-logging by default. However, holding the classloader + * references weakly ensures that the classloader will be garbage collected + * without the container performing this step. + *

+ * Limitations: + * There is still one (unusual) scenario in which a component will not + * be correctly unloaded without an explicit release. Though weak references + * are used for its keys, it is necessary to use strong references for its values. + *

+ * If the abstract class LogFactory is + * loaded by the container classloader but a subclass of + * LogFactory [LogFactory1] is loaded by the component's + * classloader and an instance stored in the static map associated with the + * base LogFactory class, then there is a strong reference from the LogFactory + * class to the LogFactory1 instance (as normal) and a strong reference from + * the LogFactory1 instance to the component classloader via + * getClass().getClassLoader(). This chain of references will prevent + * collection of the child classloader. + *

+ * Such a situation occurs when the commons-logging.jar is + * loaded by a parent classloader (e.g. a server level classloader in a + * servlet container) and a custom LogFactory implementation is + * loaded by a child classloader (e.g. a web app classloader). + *

+ * To avoid this scenario, ensure + * that any custom LogFactory subclass is loaded by the same classloader as + * the base LogFactory. Creating custom LogFactory subclasses is, + * however, rare. The standard LogFactoryImpl class should be sufficient + * for most or all users. + * + * @version $Id: WeakHashtable.java 1435077 2013-01-18 10:51:35Z tn $ + * @since 1.1 + */ +public final class WeakHashtable extends Hashtable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -1546036869799732453L; + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of all cleared entries. + */ + private static final int MAX_CHANGES_BEFORE_PURGE = 100; + + /** + * The maximum number of times put() or remove() can be called before + * the map will be purged of one cleared entry. + */ + private static final int PARTIAL_PURGE_COUNT = 10; + + /* ReferenceQueue we check for gc'd keys */ + private final ReferenceQueue queue = new ReferenceQueue(); + /* Counter used to control how often we purge gc'd entries */ + private int changeCount = 0; + + /** + * Constructs a WeakHashtable with the Hashtable default + * capacity and load factor. + */ + public WeakHashtable() {} + + /** + *@see Hashtable + */ + public boolean containsKey(Object key) { + // purge should not be required + Referenced referenced = new Referenced(key); + return super.containsKey(referenced); + } + + /** + *@see Hashtable + */ + public Enumeration elements() { + purge(); + return super.elements(); + } + + /** + *@see Hashtable + */ + public Set entrySet() { + purge(); + Set referencedEntries = super.entrySet(); + Set unreferencedEntries = new HashSet(); + for (Iterator it=referencedEntries.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + Referenced referencedKey = (Referenced) entry.getKey(); + Object key = referencedKey.getValue(); + Object value = entry.getValue(); + if (key != null) { + Entry dereferencedEntry = new Entry(key, value); + unreferencedEntries.add(dereferencedEntry); + } + } + return unreferencedEntries; + } + + /** + *@see Hashtable + */ + public Object get(Object key) { + // for performance reasons, no purge + Referenced referenceKey = new Referenced(key); + return super.get(referenceKey); + } + + /** + *@see Hashtable + */ + public Enumeration keys() { + purge(); + final Enumeration enumer = super.keys(); + return new Enumeration() { + public boolean hasMoreElements() { + return enumer.hasMoreElements(); + } + public Object nextElement() { + Referenced nextReference = (Referenced) enumer.nextElement(); + return nextReference.getValue(); + } + }; + } + + /** + *@see Hashtable + */ + public Set keySet() { + purge(); + Set referencedKeys = super.keySet(); + Set unreferencedKeys = new HashSet(); + for (Iterator it=referencedKeys.iterator(); it.hasNext();) { + Referenced referenceKey = (Referenced) it.next(); + Object keyValue = referenceKey.getValue(); + if (keyValue != null) { + unreferencedKeys.add(keyValue); + } + } + return unreferencedKeys; + } + + /** + *@see Hashtable + */ + public synchronized Object put(Object key, Object value) { + // check for nulls, ensuring semantics match superclass + if (key == null) { + throw new NullPointerException("Null keys are not allowed"); + } + if (value == null) { + throw new NullPointerException("Null values are not allowed"); + } + + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if (changeCount % PARTIAL_PURGE_COUNT == 0) { + purgeOne(); + } + + Referenced keyRef = new Referenced(key, queue); + return super.put(keyRef, value); + } + + /** + *@see Hashtable + */ + public void putAll(Map t) { + if (t != null) { + Set entrySet = t.entrySet(); + for (Iterator it=entrySet.iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + put(entry.getKey(), entry.getValue()); + } + } + } + + /** + *@see Hashtable + */ + public Collection values() { + purge(); + return super.values(); + } + + /** + *@see Hashtable + */ + public synchronized Object remove(Object key) { + // for performance reasons, only purge every + // MAX_CHANGES_BEFORE_PURGE times + if (changeCount++ > MAX_CHANGES_BEFORE_PURGE) { + purge(); + changeCount = 0; + } + // do a partial purge more often + else if (changeCount % PARTIAL_PURGE_COUNT == 0) { + purgeOne(); + } + return super.remove(new Referenced(key)); + } + + /** + *@see Hashtable + */ + public boolean isEmpty() { + purge(); + return super.isEmpty(); + } + + /** + *@see Hashtable + */ + public int size() { + purge(); + return super.size(); + } + + /** + *@see Hashtable + */ + public String toString() { + purge(); + return super.toString(); + } + + /** + * @see Hashtable + */ + protected void rehash() { + // purge here to save the effort of rehashing dead entries + purge(); + super.rehash(); + } + + /** + * Purges all entries whose wrapped keys + * have been garbage collected. + */ + private void purge() { + final List toRemove = new ArrayList(); + synchronized (queue) { + WeakKey key; + while ((key = (WeakKey) queue.poll()) != null) { + toRemove.add(key.getReferenced()); + } + } + + // LOGGING-119: do the actual removal of the keys outside the sync block + // to prevent deadlock scenarios as purge() may be called from + // non-synchronized methods too + final int size = toRemove.size(); + for (int i = 0; i < size; i++) { + super.remove(toRemove.get(i)); + } + } + + /** + * Purges one entry whose wrapped key + * has been garbage collected. + */ + private void purgeOne() { + synchronized (queue) { + WeakKey key = (WeakKey) queue.poll(); + if (key != null) { + super.remove(key.getReferenced()); + } + } + } + + /** Entry implementation */ + private final static class Entry implements Map.Entry { + + private final Object key; + private final Object value; + + private Entry(Object key, Object value) { + this.key = key; + this.value = value; + } + + public boolean equals(Object o) { + boolean result = false; + if (o != null && o instanceof Map.Entry) { + Map.Entry entry = (Map.Entry) o; + result = (getKey()==null ? + entry.getKey() == null : + getKey().equals(entry.getKey())) && + (getValue()==null ? + entry.getValue() == null : + getValue().equals(entry.getValue())); + } + return result; + } + + public int hashCode() { + return (getKey()==null ? 0 : getKey().hashCode()) ^ + (getValue()==null ? 0 : getValue().hashCode()); + } + + public Object setValue(Object value) { + throw new UnsupportedOperationException("Entry.setValue is not supported."); + } + + public Object getValue() { + return value; + } + + public Object getKey() { + return key; + } + } + + /** Wrapper giving correct symantics for equals and hashcode */ + private final static class Referenced { + + private final WeakReference reference; + private final int hashCode; + + /** + * + * @throws NullPointerException if referant is null + */ + private Referenced(Object referant) { + reference = new WeakReference(referant); + // Calc a permanent hashCode so calls to Hashtable.remove() + // work if the WeakReference has been cleared + hashCode = referant.hashCode(); + } + + /** + * + * @throws NullPointerException if key is null + */ + private Referenced(Object key, ReferenceQueue queue) { + reference = new WeakKey(key, queue, this); + // Calc a permanent hashCode so calls to Hashtable.remove() + // work if the WeakReference has been cleared + hashCode = key.hashCode(); + + } + + public int hashCode() { + return hashCode; + } + + private Object getValue() { + return reference.get(); + } + + public boolean equals(Object o) { + boolean result = false; + if (o instanceof Referenced) { + Referenced otherKey = (Referenced) o; + Object thisKeyValue = getValue(); + Object otherKeyValue = otherKey.getValue(); + if (thisKeyValue == null) { + result = otherKeyValue == null; + + // Since our hashcode was calculated from the original + // non-null referant, the above check breaks the + // hashcode/equals contract, as two cleared Referenced + // objects could test equal but have different hashcodes. + // We can reduce (not eliminate) the chance of this + // happening by comparing hashcodes. + result = result && this.hashCode() == otherKey.hashCode(); + // In any case, as our c'tor does not allow null referants + // and Hashtable does not do equality checks between + // existing keys, normal hashtable operations should never + // result in an equals comparison between null referants + } + else + { + result = thisKeyValue.equals(otherKeyValue); + } + } + return result; + } + } + + /** + * WeakReference subclass that holds a hard reference to an + * associated value and also makes accessible + * the Referenced object holding it. + */ + private final static class WeakKey extends WeakReference { + + private final Referenced referenced; + + private WeakKey(Object key, + ReferenceQueue queue, + Referenced referenced) { + super(key, queue); + this.referenced = referenced; + } + + private Referenced getReferenced() { + return referenced; + } + } +} diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/package.html b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/package.html new file mode 100644 index 000000000..534478418 --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/impl/package.html @@ -0,0 +1,22 @@ + + + +

Concrete implementations of commons-logging wrapper APIs.

+ diff --git a/sources/net.sf.j2s.java.core/unused/apache/commons/logging/package.html b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/package.html new file mode 100644 index 000000000..e9b32a50d --- /dev/null +++ b/sources/net.sf.j2s.java.core/unused/apache/commons/logging/package.html @@ -0,0 +1,255 @@ + + + +

Simple wrapper API around multiple logging APIs.

+ + +

Overview

+ +

This package provides an API for logging in server-based applications that +can be used around a variety of different logging implementations, including +prebuilt support for the following:

+
    +
  • Log4J (version 1.2 or later) + from Apache's Logging project. Each named Log + instance is connected to a corresponding Log4J Logger.
  • +
  • + JDK Logging API, included in JDK 1.4 or later systems. Each named + Log instance is connected to a corresponding + java.util.logging.Logger instance.
  • +
  • LogKit from Apache's + Avalon project. Each named Log instance is + connected to a corresponding LogKit Logger.
  • +
  • NoOpLog implementation that simply swallows + all log output, for all named Log instances.
  • +
  • SimpleLog implementation that writes all + log output, for all named Log instances, to + System.err.
  • +
+ + +

Quick Start Guide

+ +

For those impatient to just get on with it, the following example +illustrates the typical declaration and use of a logger that is named (by +convention) after the calling class: + +

+    import org.apache.commons.logging.Log;
+    import org.apache.commons.logging.LogFactory;
+
+    public class Foo {
+
+        private Log log = LogFactory.getLog(Foo.class);
+
+        public void foo() {
+            ...
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("About to do something to object " + name);
+                }
+                name.bar();
+            } catch (IllegalStateException e) {
+                log.error("Something bad happened to " + name, e);
+            }
+            ...
+        }
+
+ +

Unless you configure things differently, all log output will be written +to System.err. Therefore, you really will want to review the remainder of +this page in order to understand how to configure logging for your +application.

+ + +

Configuring the Commons Logging Package

+ + +

Choosing a LogFactory Implementation

+ +

From an application perspective, the first requirement is to retrieve an +object reference to the LogFactory instance that will be used +to create Log instances for this +application. This is normally accomplished by calling the static +getFactory() method. This method implements the following +discovery algorithm to select the name of the LogFactory +implementation class this application wants to use:

+
    +
  • Check for a system property named + org.apache.commons.logging.LogFactory.
  • +
  • Use the JDK 1.3 JAR Services Discovery mechanism (see + + http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html for + more information) to look for a resource named + META-INF/services/org.apache.commons.logging.LogFactory + whose first line is assumed to contain the desired class name.
  • +
  • Look for a properties file named commons-logging.properties + visible in the application class path, with a property named + org.apache.commons.logging.LogFactory defining the + desired implementation class name.
  • +
  • Fall back to a default implementation, which is described + further below.
  • +
+ +

If a commons-logging.properties file is found, all of the +properties defined there are also used to set configuration attributes on +the instantiated LogFactory instance.

+ +

Once an implementation class name is selected, the corresponding class is +loaded from the current Thread context class loader (if there is one), or +from the class loader that loaded the LogFactory class itself +otherwise. This allows a copy of commons-logging.jar to be +shared in a multiple class loader environment (such as a servlet container), +but still allow each web application to provide its own LogFactory +implementation, if it so desires. An instance of this class will then be +created, and cached per class loader. + + +

The Default LogFactory Implementation

+ +

The Logging Package APIs include a default LogFactory +implementation class ( +org.apache.commons.logging.impl.LogFactoryImpl) that is selected if no +other implementation class name can be discovered. Its primary purpose is +to create (as necessary) and return Log instances +in response to calls to the getInstance() method. The default +implementation uses the following rules:

+
    +
  • At most one Log instance of the same name will be created. + Subsequent getInstance() calls to the same + LogFactory instance, with the same name or Class + parameter, will return the same Log instance.
  • +
  • When a new Log instance must be created, the default + LogFactory implementation uses the following discovery + process: +
      +
    • Look for a configuration attribute of this factory named + org.apache.commons.logging.Log (for backwards + compatibility to pre-1.0 versions of this API, an attribute + org.apache.commons.logging.log is also consulted).
    • +
    • Look for a system property named + org.apache.commons.logging.Log (for backwards + compatibility to pre-1.0 versions of this API, a system property + org.apache.commons.logging.log is also consulted).
    • +
    • If the Log4J logging system is available in the application + class path, use the corresponding wrapper class + (Log4JLogger).
    • +
    • If the application is executing on a JDK 1.4 system, use + the corresponding wrapper class + (Jdk14Logger).
    • +
    • Fall back to the default simple logging implementation + (SimpleLog).
    • +
  • +
  • Load the class of the specified name from the thread context class + loader (if any), or from the class loader that loaded the + LogFactory class otherwise.
  • +
  • Instantiate an instance of the selected Log + implementation class, passing the specified name as the single + argument to its constructor.
  • +
+ +

See the SimpleLog JavaDocs for detailed +configuration information for this default implementation.

+ + +

Configuring the Underlying Logging System

+ +

The basic principle is that the user is totally responsible for the +configuration of the underlying logging system. +Commons-logging should not change the existing configuration.

+ +

Each individual Log implementation may +support its own configuration properties. These will be documented in the +class descriptions for the corresponding implementation class.

+ +

Finally, some Log implementations (such as the one for Log4J) +require an external configuration file for the entire logging environment. +This file should be prepared in a manner that is specific to the actual logging +technology being used.

+ + +

Using the Logging Package APIs

+ +

Use of the Logging Package APIs, from the perspective of an application +component, consists of the following steps:

+
    +
  1. Acquire a reference to an instance of + org.apache.commons.logging.Log, by calling the + factory method + + LogFactory.getInstance(String name). Your application can contain + references to multiple loggers that are used for different + purposes. A typical scenario for a server application is to have each + major component of the server use its own Log instance.
  2. +
  3. Cause messages to be logged (if the corresponding detail level is enabled) + by calling appropriate methods (trace(), debug(), + info(), warn(), error, and + fatal()).
  4. +
+ +

For convenience, LogFactory also offers a static method +getLog() that combines the typical two-step pattern:

+
+  Log log = LogFactory.getFactory().getInstance(Foo.class);
+
+

into a single method call:

+
+  Log log = LogFactory.getLog(Foo.class);
+
+ +

For example, you might use the following technique to initialize and +use a Log instance in an application component:

+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class MyComponent {
+
+  protected Log log =
+    LogFactory.getLog(MyComponent.class);
+
+  // Called once at startup time
+  public void start() {
+    ...
+    log.info("MyComponent started");
+    ...
+  }
+
+  // Called once at shutdown time
+  public void stop() {
+    ...
+    log.info("MyComponent stopped");
+    ...
+  }
+
+  // Called repeatedly to process a particular argument value
+  // which you want logged if debugging is enabled
+  public void process(String value) {
+    ...
+    // Do the string concatenation only if logging is enabled
+    if (log.isDebugEnabled())
+      log.debug("MyComponent processing " + value);
+    ...
+  }
+
+}
+
+ +