summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew John Hughes <gnu_andrew@member.fsf.org>2006-11-29 22:43:35 +0000
committerAndrew John Hughes <gnu_andrew@member.fsf.org>2006-11-29 22:43:35 +0000
commit3be294e0e9f07e379e1499b9868db9855eb8726f (patch)
treea1f67d3f1469ecc142b11c30b1e3c2e1a624794d
parentf40b961c6c82ebc9ae9863635e5fe19c0b07c116 (diff)
downloadclasspath-3be294e0e9f07e379e1499b9868db9855eb8726f.tar.gz
2006-11-29 Andrew John Hughes <gnu_andrew@member.fsf.org>
* Merge of HEAD-->generics for 2006/11/12-2006/11/29.
-rw-r--r--AUTHORS11
-rw-r--r--ChangeLog1207
-rw-r--r--configure.ac3
-rw-r--r--doc/api/Makefile.am2
-rw-r--r--doc/vmintegration.texinfo130
-rw-r--r--doc/www.gnu.org/newsitems.txt7
-rw-r--r--gnu/java/awt/font/autofit/AxisHints.java45
-rw-r--r--gnu/java/awt/font/autofit/Constants.java61
-rw-r--r--gnu/java/awt/font/autofit/GlyphHints.java75
-rw-r--r--gnu/java/awt/font/autofit/Latin.java177
-rw-r--r--gnu/java/awt/font/autofit/LatinAxis.java53
-rw-r--r--gnu/java/awt/font/autofit/LatinMetrics.java51
-rw-r--r--gnu/java/awt/font/autofit/Scaler.java52
-rw-r--r--gnu/java/awt/font/autofit/Script.java62
-rw-r--r--gnu/java/awt/font/autofit/ScriptMetrics.java49
-rw-r--r--gnu/java/awt/font/autofit/Segment.java47
-rw-r--r--gnu/java/awt/font/autofit/Width.java46
-rw-r--r--gnu/java/awt/font/opentype/CharGlyphMap.java2
-rw-r--r--gnu/java/awt/font/opentype/OpenTypeFont.java17
-rw-r--r--gnu/java/awt/font/opentype/Scaler.java12
-rw-r--r--gnu/java/awt/font/opentype/truetype/GlyphLoader.java5
-rw-r--r--gnu/java/awt/font/opentype/truetype/TrueTypeScaler.java6
-rw-r--r--gnu/java/awt/font/opentype/truetype/Zone.java2
-rw-r--r--gnu/java/awt/java2d/QuadSegment.java69
-rw-r--r--gnu/java/awt/java2d/TexturePaintContext.java6
-rw-r--r--gnu/java/awt/peer/GLightweightPeer.java32
-rw-r--r--gnu/java/awt/peer/gtk/BufferedImageGraphics.java122
-rw-r--r--gnu/java/awt/peer/gtk/CairoGraphics2D.java295
-rw-r--r--gnu/java/awt/peer/gtk/CairoSurface.java5
-rw-r--r--gnu/java/awt/peer/gtk/ComponentGraphics.java51
-rw-r--r--gnu/java/awt/peer/gtk/FreetypeGlyphVector.java4
-rw-r--r--gnu/java/awt/peer/gtk/GdkFontMetrics.java157
-rw-r--r--gnu/java/awt/peer/gtk/GdkFontPeer.java217
-rw-r--r--gnu/java/awt/peer/gtk/GtkComponentPeer.java56
-rw-r--r--gnu/java/awt/peer/gtk/GtkToolkit.java51
-rw-r--r--gnu/java/awt/peer/headless/HeadlessGraphicsEnvironment.java32
-rw-r--r--gnu/java/awt/peer/swing/SwingComponentPeer.java82
-rw-r--r--gnu/java/net/PlainSocketImpl.java3
-rw-r--r--gnu/java/util/regex/RE.java8
-rw-r--r--gnu/java/util/regex/RESyntax.java11
-rw-r--r--gnu/javax/management/Server.java2163
-rw-r--r--gnu/javax/swing/text/html/css/CSSColor.java13
-rw-r--r--gnu/javax/swing/text/html/css/CSSParser.java25
-rw-r--r--gnu/javax/swing/text/html/css/CSSParserCallback.java2
-rw-r--r--gnu/javax/swing/text/html/css/FontSize.java129
-rw-r--r--gnu/javax/swing/text/html/css/Selector.java2
-rw-r--r--gnu/javax/swing/text/html/parser/htmlValidator.java5
-rw-r--r--gnu/javax/swing/text/html/parser/support/Parser.java35
-rw-r--r--gnu/javax/swing/text/html/parser/support/low/Constants.java11
-rw-r--r--gnu/javax/swing/text/html/parser/support/textPreProcessor.java20
-rw-r--r--include/gnu_java_awt_peer_gtk_CairoGraphics2D.h2
-rw-r--r--java/awt/Component.java11
-rw-r--r--java/awt/EventQueue.java7
-rw-r--r--java/awt/Toolkit.java21
-rw-r--r--java/awt/dnd/DropTarget.java124
-rw-r--r--java/awt/font/TextHitInfo.java5
-rw-r--r--java/awt/font/TextLayout.java1008
-rw-r--r--java/awt/image/ImageFilter.java351
-rw-r--r--java/awt/image/IndexColorModel.java77
-rw-r--r--java/awt/image/RGBImageFilter.java416
-rw-r--r--java/awt/image/ReplicateScaleFilter.java144
-rw-r--r--java/awt/image/SampleModel.java29
-rw-r--r--java/awt/image/SinglePixelPackedSampleModel.java121
-rw-r--r--java/awt/image/WritableRaster.java19
-rw-r--r--java/beans/DesignMode.java84
-rw-r--r--java/beans/Statement.java22
-rw-r--r--java/beans/beancontext/BeanContextServicesSupport.java577
-rw-r--r--java/beans/beancontext/BeanContextSupport.java335
-rw-r--r--java/io/File.java3
-rw-r--r--java/net/DatagramSocket.java6
-rw-r--r--java/text/Bidi.java3
-rw-r--r--java/text/DecimalFormat.java3007
-rw-r--r--java/text/DecimalFormatSymbols.java2
-rw-r--r--java/text/NumberFormat.java14
-rw-r--r--java/util/jar/JarEntry.java29
-rw-r--r--java/util/jar/JarFile.java202
-rw-r--r--javax/management/MBeanPermission.java562
-rw-r--r--javax/management/MBeanRegistration.java95
-rw-r--r--javax/management/MBeanTrustPermission.java105
-rw-r--r--javax/swing/ButtonGroup.java4
-rw-r--r--javax/swing/JComponent.java7
-rw-r--r--javax/swing/JEditorPane.java183
-rw-r--r--javax/swing/JRootPane.java12
-rw-r--r--javax/swing/JSlider.java2
-rw-r--r--javax/swing/RepaintManager.java8
-rw-r--r--javax/swing/plaf/basic/BasicTextUI.java62
-rw-r--r--javax/swing/plaf/metal/MetalIconFactory.java60
-rw-r--r--javax/swing/text/AbstractDocument.java203
-rw-r--r--javax/swing/text/BoxView.java263
-rw-r--r--javax/swing/text/CompositeView.java35
-rw-r--r--javax/swing/text/DefaultStyledDocument.java113
-rw-r--r--javax/swing/text/FlowView.java2
-rw-r--r--javax/swing/text/GapContent.java548
-rw-r--r--javax/swing/text/GlyphView.java177
-rw-r--r--javax/swing/text/StyleContext.java22
-rw-r--r--javax/swing/text/Utilities.java117
-rw-r--r--javax/swing/text/html/BlockView.java16
-rw-r--r--javax/swing/text/html/CSS.java9
-rw-r--r--javax/swing/text/html/HTMLDocument.java93
-rw-r--r--javax/swing/text/html/HTMLEditorKit.java203
-rw-r--r--javax/swing/text/html/ImageView.java295
-rw-r--r--javax/swing/text/html/ParagraphView.java16
-rw-r--r--javax/swing/text/html/StyleSheet.java245
-rw-r--r--javax/swing/text/html/TableView.java194
-rw-r--r--lib/Makefile.am16
-rwxr-xr-xlib/gen-classlist.sh.in6
-rw-r--r--native/jni/gtk-peer/gdkfont.h5
-rw-r--r--native/jni/gtk-peer/gnu_java_awt_peer_gtk_CairoGraphics2D.c11
-rw-r--r--native/jni/gtk-peer/gnu_java_awt_peer_gtk_GdkFontPeer.c92
-rw-r--r--native/jni/java-lang/java_lang_VMDouble.c329
-rw-r--r--native/jni/java-net/gnu_java_net_VMPlainSocketImpl.c8
-rw-r--r--native/jni/java-net/java_net_VMNetworkInterface.c4
-rw-r--r--native/jni/java-nio/gnu_java_nio_VMSelector.c12
-rw-r--r--native/jni/midi-dssi/Makefile.am5
-rw-r--r--native/target/.cvsignore8
-rw-r--r--native/target/Linux/.cvsignore8
-rw-r--r--native/target/generic/.cvsignore8
-rw-r--r--tools/gnu/classpath/tools/appletviewer/TagParser.java56
-rw-r--r--tools/gnu/classpath/tools/getopt/package.html49
119 files changed, 12660 insertions, 3955 deletions
diff --git a/AUTHORS b/AUTHORS
index 1be8b4406..4efc086df 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -9,6 +9,7 @@ Anthony Balkissoon (abalkiss@redhat.com)
Stuart Ballard (stuart.a.ballard@gmail.com)
Mark Benvenuto (mcb54@columbia.edu)
Gary Benson (gbenson@redhat.com)
+Tania Bento (tbento@redhat.com)
Geoff Berry (gcb@gnu.org)
James E. Blair (corvus@gnu.org)
Eric Blake (ebb9@email.byu.edu)
@@ -17,6 +18,8 @@ Chris Burdess (dog@gnu.org)
David Daney (ddaney@avtrex.com)
Nic Ferrier (nferrier@tapsellferrier.co.uk)
Paul Fisher (rao@gnu.org)
+Thomas Fitzsimmons (fitzsim@redhat.com)
+Jeroen Frijters (jeroen@frijters.net)
David Gilbert (david.gilbert@object-refinery.com)
Anthony Green (green@redhat.com)
Jochen Hoenicke (Jochen.Hoenicke@Informatik.Uni-Oldenburg.de)
@@ -24,9 +27,10 @@ Andrew John Hughes (gnu_andrew@member.fsf.org)
Kazumitsu Ito (kaz@maczuka.gcd.org)
Olivier Jolly (olivier.jolly@pcedev.com)
Brian Jones (cbj@gnu.org)
-Roman Kennke (roman@kennke.org)
-Michael Koch (konqueror@gmx.de)
John Keiser (jkeiser@iname.com)
+Roman Kennke (roman@kennke.org)
+Michael Koch (konqueror@gmx.de)
+Francis Kung (fkung@redhat.com)
John Leuner (jewel@debian.org)
Warren Levy (warrenl@cygnus.com)
Sven de Marothy (sven@physto.se)
@@ -37,6 +41,9 @@ Raif S. Naffah (raif@swiftdsl.com.au)
Aaron M. Renn (arenn@urbanophile.com)
Andrew Selkirk (aselkirk@sympatico.ca)
Christian Thalinger (twisti@complang.tuwien.ac.at)
+Andreas Tobler (a.tobler@schweiz.org)
+Mario Torre (neugens@limasoftware.net)
+Dalibor Topic (robilad@kaffe.org)
Tom Tromey (tromey@cygnus.com)
Ronald Veldema (rveldema@cs.vu.nl)
Mark Wielaard (mark@klomp.org)
diff --git a/ChangeLog b/ChangeLog
index 1ac124999..39ce2d3d8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,13 +1,479 @@
+2006-11-29 Tania Bento <tbento@redhat.com>
+
+ * tools/gnu/classpath/tools/appletviewer/TagParser.java:
+ (parseParams): Unescape 'val' before putting it into the Map.
+ (unescapeString): New private method.
+
+2006-11-29 Tom Tromey <tromey@redhat.com>
+
+ * tools/gnu/classpath/tools/getopt/package.html: New file.
+
+2006-11-29 David Gilbert <david.gilbert@object-refinery.com>
+
+ * javax/swing/plaf/metal/MetalIconFactory.java
+ (HorizontalSliderThumbIcon.paintIcon()): Commented out gradient paint,
+ (VerticalSliderThumbIcon.paintIcon()): Likewise.
+
+2006-11-29 Mario Torre <neugens@limasoftware.net>
+
+ * java/text/NumberFormat.java (getCurrencyInstance): Replaced dollar sign
+ with the default international currency sign \u00A4.
+ * java/text/DecimalFormat.java (scanFix): Fix to use the localized symbol
+ table for string formatting.
+ (formatInternal): likewise.
+ (scanNegativePattern): likewise.
+ (applyPattern): likewise.
+ * java/text/DecimalFormatSymbols.java (clone): Revert to old version as
+ Locale is immutable and does not need clone.
+
+2006-11-29 Francis Kung <fkung@redhat.com>
+
+ * gnu/java/awt/peer/gtk/CairoGraphics2D.java
+ (drawLine): Remove hard-coded pixel shifting.
+
+2006-11-29 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/Component.java
+ (isShowing): Simplified condition code and avoid unnecessary
+ if-codepaths.
+ (coalesceEvents): Always coalesce paint events and let the peer
+ figure out the expanding of the repaint area.
+ * gnu/java/awt/peer/swing/SwingComponentPeer.java
+ (currentPaintEvents): Removed. Replaced by paintArea.
+ (paintArea): New field. Tracks the dirty area.
+ (SwingComponentPeer): Removed init of currentPaintEvents.
+ (coalescePaintEvent): Simplified to only union the dirty regions.
+ (handleEvent): Paint dirty region that was tracked in paintArea.
+ * gnu/java/awt/peer/gtk/GtkComponentPeer.java
+ (paintArea): New field. Tracks the dirty region.
+ (coalescePaintEvent): Implemented to track the dirty region.
+ (paintComponent): Use the dirty region in paintArea. Protect
+ state by putting the paint and dispose code in a try-finally.
+ (updateComponent): Use the dirty region in paintArea. Protect
+ state by putting the paint and dispose code in a try-finally.
+
+2006-11-29 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/font/TextLayout.java
+ (getVisualHighlightShape): Removed debug output.
+
2006-11-28 Andrew Haley <aph@redhat.com>
* vm/reference/sun/reflect/misc/ReflectUtil.java
(checkPackageAccess): Implement.
+2006-11-28 Dalibor Topic <robilad@kaffe.org>
+
+ * native/jni/java-lang/java_lang_VMDouble.c:
+ (parseDoubleFromChars) New function. Factored out from ...
+ (Java_java_lang_VMDouble_parseDouble): Factored out the parsing.
+ (dtoa_toString): New function. Factored out from ...
+ (Java_java_lang_VMDouble_toString) : Factored out the conversion.
+ Changed conversion mode to 2, as modes 0 and 1 don't round
+ as the API spec demands. Invoke conversion function as often
+ as necessary with growing precision until a reversible
+ representation of the double in form of a string is reached.
+
+2006-11-28 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/JComponent.java
+ (putClientProperty): Do not fire event when both old and new
+ value are == null.
+
2006-11-27 Andrew John Hughes <gnu_andrew@member.fsf.org>
* java/lang/Enum.java:
Make name and ordinal final.
+2006-11-27 Casey Marshall <csm@gnu.org>
+
+ * java/util/jar/JarEntry.java (certs): removed.
+ (jarfile): new field.
+ (getCertificates): read the certificates from the containing JarFile.
+ * java/util/jar/JarFile.java (JarEnumeration.nextElement): don't
+ fill in 'certs,' fill in 'jarfile' for the entry.
+ (getEntry): likewise.
+
+2006-11-27 Francis Kung <fkung@redhat.com>
+
+ * java/awt/image/WritableRaster.java
+ (createChild): Implemented.
+
+2006-11-27 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/font/TextLayout.java
+ (TextLayout(TextLayout,int,int)): Also layout the new runs.
+ (getVisualHighlightShape): Implemented.
+ (layoutRuns): Fixed boundary so that the last run is also laid out.
+ (left): New helper method.
+ (right): New helper method.
+
+2006-11-27 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/font/TextLayout.java
+ (getCaretShape(TextHitInfo,Rectangle2D)): Implemented.
+ (getCaretShape(TextHitInfo)): Use natural bounds.
+ (getCaretShapes(int,Rectangle2D,CaretPolicy)): New API method.
+ (getCaretShapes(int,Rectangle2D)): Delegate to new method
+ above with DEFAULT_CARET_POLICY.
+ (getCaretShapes(int)): Use natural bounds.
+
+2006-11-27 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/font/TextLayout.java
+ (Run.font): New field.
+ (Run.location): New field.
+ (Run.Run): Initialize font.
+ (font): Removed field. This is moved into Run as the actual font
+ is something run-specific.
+ (TextLayout(String,Font,FontRenderContext)): Set font on the
+ single runs. Layout the runs here.
+ (TextLayout(TextLayout,int,int)): Copy over the run fonts.
+ (findRunAtIndex): New helper method.
+ (getCaretInfo): Implemented.
+ (layoutRuns): New helper method.
+ (toString): Don't put font in output string.
+
+2006-11-27 Raif S. Naffah <classpath@naffah-raif.name>
+
+ * AUTHORS: Added Jeroen Fritjers.
+
+2006-11-27 neugens <neugens@nirvana.limasoftware.net>
+
+ * java/text/DecimalFormat.java (formatInternal): Add an explicit test
+ for FieldPosition to be null.
+ Check if the factional part is just 0 and can be omitted from the
+ result.
+ (scanNegativePattern): Fixed index out of bound exception when searching
+ for international currency symbol in negative pattern.
+
+2006-11-27 Andrew John Hughes <gnu_andrew@member.fsf.org>
+
+ * java/beans/beancontext/BeanContextSupport.java:
+ (readObject(ObjectInputStream)): Implemented.
+ (writeObject(ObjectOutputStream)): Likewise.
+ (BCSChild.getTargetChild()): Added.
+ (bcsPreDeserializationHook()): Implemented.
+ (bcsPreSerializationHook()): Likewise.
+ (childDeserializedHook(Object,BCSChild)): Likewise.
+ (isSerializing()): Likewise.
+ (readChildren(ObjectInputStream)): Likewise.
+ (writeChildren(ObjectOutputStream)): Likewise.
+
+2006-11-26 Roger Sayle <roger <at> eyesopen.com>
+ Ian Lance Taylor <ian <at> airs.com>
+ Paolo Bonzini <bonzini <at> gnu.org>
+
+ Fixes bug #25557.
+
+ * lib/gen-classlist.sh.in: Avoid using test's -ef operator for
+ increased portability. Likewise, use -f instead of -e.
+
+2006-11-26 Mark Wielaard <mark@klomp.org>
+
+ * lib/Makefile.am (propertydirs): Removed.
+ (resources): Explicitly create all dirs.
+
+2006-11-26 Mark Wielaard <mark@klomp.org>
+
+ * gnu/java/net/PlainSocketImpl.java (accept): Reset timeout on Socket.
+
+2006-11-26 Dalibor Topic <robilad@kaffe.org>
+
+ * native/target/.cvsignore,
+ native/target/generic/.cvsignore,
+ native/target/Linux/.cvsignore:
+ Removed no longer used files.
+
+ * native/target: Removed no longer used directory.
+
+2006-11-26 Dalibor Topic <robilad@kaffe.org>
+
+ Fixes bug #29133.
+
+ * libraries/clib/nio/gnu_java_nio_VMSelector.c
+ (Java_gnu_java_nio_VMSelector_select):
+ Use strerror if strerror_r is not available.
+
+ Reported by: Michael Franz <mvfranz@gmail.com>,
+ Riccardo Mottola <zuse@libero.it>
+
+2006-11-26 Dalibor Topic <robilad@kaffe.org>
+
+ Fixes bug #26756.
+
+ * native/jni/midi-dssi/Makefile.am (AM_CFLAGS): Removed
+ STRICT_WARNING_CFLAGS since it caused the build to fail
+ on GNU/Linux.
+
+2006-11-26 Ian Rogers <ian.rogers@manchester.ac.uk>
+
+ * doc/vmintegration.texinfo: Update VM Threading Model section.
+
+2006-11-26 Tom Tromey <tromey@redhat.com>
+
+ * native/jni/java-net/java_net_VMNetworkInterface.c: Conditionally
+ include ifaddrs.h.
+ (Java_java_net_VMNetworkInterface_getVMInterfaces): Updated
+ conditional.
+ * native/jni/java-net/gnu_java_net_VMPlainSocketImpl.c:
+ Conditionally include ifaddrs.h.
+ (getif_address): Updated conditional.
+ (getif_index): Likewise.
+ * configure.ac: Check for ifaddrs.h.
+
+2006-11-25 Mark Wielaard <mark@klomp.org>
+
+ * java/io/File.java (list): Return empty list for unreadable dirs.
+
+2006-11-25 Mark Wielaard <mark@klomp.org>
+
+ * gnu/java/awt/peer/gtk/CairoGraphics2D.java (drawGlyphVector):
+ Synchronize on font peer.
+ (setFont): Likewise.
+ * gnu/java/awt/peer/gtk/GdkFontPeer.java (getFontMetrics): Mark
+ synchronized.
+ (getTextMetrics): Likewise.
+
+2006-11-25 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/GapContent.java
+ (getPositionsInRange): Rewritten to use the more efficient
+ binary search searchFirst() and avoid an NPE that was caused
+ by GC'ed positions.
+
+2006-11-25 Mark Wielaard <mark@klomp.org>
+
+ * javax/swing/text/CompositeView.java (modelToView): Never return
+ null.
+
+2006-11-25 Mark Wielaard <mark@klomp.org>
+
+ * javax/swing/text/html/TableView.java (calculateColumnRequirements):
+ Check whether rowView instanceof RowView.
+ (updateGrid): Likewise.
+
+2006-11-25 Mario Torre <neugens@nirvana.limasoftware.net>
+
+ PR28462
+ * java/text/DecimalFormat.java: Almost new rewrite, and update to 1.5.
+ * java/text/NumberFormat.java (format): all format methods, fixed
+ FieldPosition argument should never be null.
+ (format(Object, StringBuffer, FieldPosition)): fixed signature,
+ method is not final.
+ * java/text/DecimalFormatSymbols.java (clone): fixed to also clone
+ locale.
+ * AUTHORS: added my name to the file.
+
+2006-11-25 Mark Wielaard <mark@klomp.org>
+
+ * javax/swing/text/html/StyleSheet.java (paint): Guard against
+ getChildAllocation() returning null.
+
+2006-11-25 Mark Wielaard <mark@klomp.org>
+
+ * gnu/javax/swing/text/html/css/Selector.java (calculateSpecificity):
+ Use clazzIndex for id substring.
+
+2006-11-25 Mark Wielaard <mark@klomp.org>
+
+ * java/awt/EventQueue.java (pop): Only terminate dispatchThread when
+ it is still running.
+
+2006-11-25 Mark Wielaard <mark@klomp.org>
+
+ Fixes bug #28822
+ * doc/api/Makefile.am (create_html): Guard GJDOC invocation with
+ CREATE_API_DOCS
+
+2006-11-24 Tania Bento <tbento@redhat.com>
+
+ * java/awt/font/TextHitInfo.java
+ (equals(TextHitInfo)): If TextHitInfo parameter is null, return false.
+ (beforeOffset): Decreased first parameter by 1.
+
+2006-11-24 Francis Kung <fkung@redhat.com>
+
+ * gnu/java/awt/peer/gtk/BufferedImageGraphics.java
+ (constructor): Check sample model when setting fastCM flag.
+ (updateBufferedImage): Check scanline and sample model offsets before
+ copying data directly into the image data buffer.
+
+2006-11-24 Francis Kung <fkung@redhat.com>
+
+ * gnu/java/awt/java2d/QuadSegment.java
+ (offsetSubdivided): Handle special straight-line cases.
+
+2006-11-24 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/dnd/DropTarget.java
+ (DropTargetAutoScroller.HYSTERESIS): New constant.
+ (DropTargetAutoScroller.DELAY): New constant.
+ (DropTargetAutoScroller.inner): New field. A cached
+ Rectangle instance.
+ (DropTargetAutoScroller.outer): New field. A cached
+ Rectangle instance.
+ (DropTargetAutoScroller.timer): New field. The actual timer.
+ (DropTargetAutoScroller.DropTargetAutoScroller):
+ Initialize timer.
+ (DropTargetAutoScroller.actionPerformed): Implemented.
+ (DropTargetAutoScroller.stop): Implemented.
+ (DropTargetAutoScroller.updateLocation): Implemented.
+ (clearAutoscroll): Stop the autoscroller before nullifying it.
+ (createDropTargetAutoScroller): Don't set the field here,
+ only return a new instance.
+ (dragEnter): Only do something when active. Initialize
+ auto scrolling.
+ (dragExit): Only do something when active. Stop auto scrolling.
+ (dragOver): Only do something when active. Update auto scrolling.
+ (drop): Only do something when active. Update auto scrolling.
+ (dropActionChanged): Only do something when active. Update
+ auto scrolling.
+ (initializeAutoScrolling): Check if component is an instance
+ of Autoscroll, otherwise do nothing.
+ (setActive): Disable autoscrolling when deactivating.
+ (setComponent): When component is set to null, disable autoscrolling.
+
+2006-11-24 David Gilbert <david.gilbert@object-refinery.com>
+
+ * java/beans/beancontext/BeanContextServicesSupport.java
+ (getChildBeanContextServicesListener): Implemented.
+
+2006-11-23 Roman Kennke <kennke@aicas.com>
+
+ * gnu/java/awt/peer/headless/HeadlessGraphicsEnvironment.java
+ (createGraphics): Use constructor to create new instance of
+ BufferedImageGraphics.
+ * java/awt/Toolkit.java
+ (getDefaultToolkit): Really try to get a real toolkit. Only
+ use HeadlessToolkit if no other is available.
+ * gnu/java/awt/peer/gtk/GtkToolkit.java
+ (checkHeadless): New helper method. Checks for headless environment
+ and throws HeadlessException if appropriate.
+ (createButton): Check for headless.
+ (createCanvas): Check for headless.
+ (createCheckbox): Check for headless.
+ (createCheckboxMenuItem): Check for headless.
+ (createChoice): Check for headless.
+ (createDialog): Check for headless.
+ (createDragGestureRecognizer): Check for headless.
+ (createDragSourceContextPeer): Check for headless.
+ (createEmbeddedWindow): Check for headless.
+ (createFileDialog): Check for headless.
+ (createFrame): Check for headless.
+ (createCheckbox): Check for headless.
+ (createLabel): Check for headless.
+ (createList): Check for headless.
+ (createMenu): Check for headless.
+ (createMenuBar): Check for headless.
+ (createMenuItem): Check for headless.
+ (createPanel): Check for headless.
+ (createPopupMenu): Check for headless.
+ (createScrollbar): Check for headless.
+ (createScrollPane): Check for headless.
+ (createTextArea): Check for headless.
+ (createTextField): Check for headless.
+ (createWindow): Check for headless.
+
+2006-11-23 David Gilbert <david.gilbert@object-refinery.com>
+
+ * java/beans/beancontext/BeanContextSupport.java
+ (deserialize): Implemented,
+ (serialize): Implemented.
+
+2006-11-23 Roman Kennke <kennke@aicas.com>
+
+ * gnu/java/awt/peer/headless/HeadlessGraphicsEnvironment.java
+ (createGraphics): Try to use Cairo graphics if available.
+
+2006-11-22 David Gilbert <david.gilbert@object-refinery.com>
+
+ * java/beans/beancontext/BeanContextSupport.java
+ (toArray): Added API docs,
+ (toArray(Object[])): Added API docs, removed NotImplementedException.
+
+2006-11-22 Tania Bento <tbento@redhat.com>
+
+ * javax/swing/JRootPane.java
+ (setLayeredPane): Added documentation; throw
+ IllegalComponentStateException if layered pane parameter is null.
+
+2006-11-22 David Gilbert <david.gilbert@object-refinery.com>
+
+ * java/beans/beancontext/BeanContextSupport.java
+ (avoidingGui): Removed NotImplementedException.
+
+2006-11-22 Francis Kung <fkung@redhat.com>
+
+ * gnu/java/awt/peer/gtk/BufferedImageGraphics.java
+ (drawGlyphVector): Clip updated area to glyph bounds.
+ * gnu/java/awt/peer/gtk/CairoGraphics2D.java
+ (createPath): Eliminate distortion when pixel-shifting rectangles; separate
+ x-coordinate and y-coordinate pixel shifting.
+ (shifted): Removed method.
+ (shiftX): New method, recognising scaling transforms.
+ (shiftY): New method, recognising scaling transforms.
+ (walkPath): Separate x-coordinate and y-coordinate pixel shifting.
+
+2006-11-22 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/font/TextLayout.java
+ (hash): New field. Caches the hash code.
+ (hashCode): Implemented.
+
+2006-11-22 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/image/ImageFilter.java
+ Reformat whole class.
+ (getFilterInstance): Don't touch the consumer field. Don't check
+ consumer.
+ (imageComplete): Don't check consumer.
+ (setColorModel): Don't check consumer.
+ (setDimensions): Don't check consumer.
+ (setHints): Don't check consumer.
+ (setPixels): Don't check consumer.
+ (setProperties): Pass the original property too.
+ * java/awt/image/IndexColorModel.java
+ (IndexColorModel(int,int,byte[],byte[],byte[],int)): Set the
+ transparent pixel by calling the new helper method.
+ (IndexColorModel(int,int,byte[],int,boolean,int)): Set the
+ transparent pixel by calling the new helper method.
+ (IndexColorModel(int,int,int[],int,boolean,int,int)): Set the
+ transparent pixel by calling the new helper method.
+ (coerceData): Removed. This is not needed.
+ (getAlpha): Simply return value from color map. The transparent
+ pixel has to be there.
+ (setTransparentPixel): New helper method. Inserts the transparent
+ pixel.
+ * java/awt/image/RGBImageFilter.java
+ Reformat whole class.
+ (convertColorModelToDefault): Removed. No longer needed.
+ (filterIndexColorModel): Don't handle transparent pixels
+ separately.
+ (filterRGBPixels): Set pixels on consumer already.
+ (makeColor): Removed. No longer needed.
+ * java/awt/image/ReplicateScaleFilter.java
+ (replicatePixels): Removed.
+ (setDimension): Correctly compute destination size, avoid double
+ calculations.
+ (setPixels): Avoid double calculations. Fixed some boundary cases.
+ (setupSources): New helper method.
+ * java/awt/image/SampleModel.java
+ (setDataElements): Also handle TYPE_SHORT, TYPE_FLOAT
+ and TYPE_DOUBLE.
+ * java/awt/image/SinglePixelPackedSampleModel.java
+ (setDataElements(int,int,int,int,Object,DataBuffer)): Removed.
+ This is not needed as the superclass already copies line
+ by line.
+ (setDataElements(int,int,Object,DataBuffer)): Simplified code,
+ removed some checks that the RI also doesn't perform. Call
+ DataBuffer.setElem().
+
+2006-11-22 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/text/TextLayout.java
+ (getLogicalRangesForVisualSelection): Implemented.
+
2006-11-21 Andrew John Hughes <gnu_andrew@member.fsf.org>
* sun/reflect/annotation/AnnotationParser.java,
@@ -19,11 +485,752 @@
* sun/misc/Service.java:
Implemented.
+2006-11-21 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/text/TextLayout.java
+ (Run.isLeftToRight): New helper method.
+ (logicalToVisual): New field. Maps logical indices to visual
+ indices.
+ (visualToLogical): New field. Maps visual indices to logical
+ indices.
+ (TextLayout): Setup mappings.
+ (setupMappings): New method for setting up the mappings.
+ (getCharacterLevel): Reorganized code.
+ (getNextLeftHit(int)): Implemented.
+ (getNextLeftHit(int,CaretPolicy)): New method.
+ (getNextLeftHit(TextHitInfo)): Implemented.
+ (getNextRightHit(int)): Implemented.
+ (getNextRightHit(int,CaretPolicy)): New method.
+ (getNextRightHit(TextHitInfo)): New method.
+ (getVisualOtherHit): Implemented.
+ (checkHitInfo): New helper methods for checking parameters.
+ (hitToCaret): New helper method. Maps hit infos to caret locations.
+ (caretToHit): New helper method. Maps caret locations to hit infos.
+ (isCharacterLTR): New helper method.
+ (CaretPolicy.getStrongCaret): Implemented.
+
+2006-11-21 Francis Kung <fkung@redhat.com>
+
+ * gnu/java/awt/peer/gtk/BufferedImageGraphics.java
+ (draw): Include stroke width when calculating bounds.
+ (updateBufferedImage): Round bounds more generously, handle negative
+ height/width values, and clip more intelligently.
+ * gnu/java/awt/peer/gtk/CairoGraphics2D.java
+ (createPath): Add shortcut optimization for lines.
+ (draw): Include stroke width when calculating bounds.
+ (drawLine): Delegate to main draw() method.
+ (drawRect): Likewise.
+ (fillRect): Delegate to main fill() method.
+ (findStrokedBounds): New method.
+ (setCustomPaint): Round bounds more generously.
+ * gnu/java/awt/peer/gtk/ComponentGraphics.java
+ (drawLine): Removed.
+ (drawRect): Removed.
+ (fillRect): Removed.
+
+2006-11-21 Francis Kung <fkung@redhat.com>
+
+ * gnu/java/awt/java2d/TexturePaintContext.java
+ (getRaster): Handle negative coordinate values.
+ * gnu/java/awt/peer/gtk/CairoGraphics2D.java
+ (setPaint): Moved custom paint processing to a new method.
+ (setPaintPixels): Added x, y parameters.
+ (getRealBounds): Added documentation.
+ (copy): Copy clipping information.
+ (drawLine): Process custom paints.
+ (setCustomPaint): New method.
+ (fill): Process custom paints.
+ (drawGlyphVector): Process custom paints.
+ (drawRect): Process custom paints.
+ (draw): Process custom paints.
+ * gnu/java/awt/peer/gtk/CairoSurface.java
+ (cairoCM_opaque): New constant.
+ * gnu/java/awt/peer/gtk/BufferedImageGraphics.java
+ (argb32): Removed constant.
+ (rgb32): Removed constant.
+ (BufferedImageGraphics(BufferedImage)): Updated constant names.
+ (BufferedImageGraphics(BufferedImageGraphics)): Copy color model flags.
+ (updateBufferedImage): Transform to device-space before updating.
+ * include/gnu_java_awt_peer_gtk_CairoGraphics2D.h
+ (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_setPaintPixels): Added x, y
+ parameters.
+ * native/jni/gtk-peer/gnu_java_awt_peer_gtk_CairoGraphics2D.c
+ (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_setPaintPixels): Set pattern
+ source at designated x, y origin.
+
+2006-11-21 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/text/TextLayout.java
+ (Run): New inner helper class.
+ (length): New field.
+ (naturalBounds): New field.
+ (offset): New field.
+ (runIndices): Removed. This is now encapsulate in a Run object.
+ (runs): Changed to Run[].
+ (string): Changed to char[].
+ (totalAdvance): New field. Caches advance value.
+ (TextLayout(String,Font,FontRenderContext)): Change to store
+ string as char[] and run layout as Run[]. Clean out empty
+ run items.
+ (TextLayout(TextLayout,int,int)): Change to store
+ string as char[] and run layout as Run[].
+ (clone): Call private constructor for maximum efficiency.
+ (determineWhitespace): Adapted to use char[] data.
+ (draw): Adapted to use Run objects.
+ (getAdvance): Cache computed total advance.
+ (getBlackBoxBounds): Adapted to use Run objects.
+ (getCaretInfo): Use natural layout bounds.
+ (getCharacterCount): Return length field.
+ (getLogicalHighlightShape): Adapted to use Run objects.
+ (getNaturalBounds): New helper method. Calculates and returns the
+ natural bounds of this text layout.
+ (getOutline): Adapted to use Run objects.
+ (getStringProperties): Adapted to use char[] data.
+ (getVisibleAdvance): Adapted to use char[] and Run data.
+ (handleJustify): Adapted to use char[] and Run data.
+ (hitTestChar(float,float,Rectangle2D)): Implemented.
+ (hitTestChar(float,float)): Use natural bounds.
+ (setCharIndices): Adapted to use char[] and Run data.
+ (toString): Adapted to use char[] and Run data.
+ * java/text/Bidi.java
+ (requiresBidi): Exclude paragraph separators from bidi-triggers.
+
+2006-11-21 Roman Kennke <kennke@aicas.com>
+
+ * gnu/java/awt/peer/gtk/GdkFontMetrics.java:
+ Removed. This is now an inner class in GdkFontPeer.
+ * gnu/java/awt/peer/gtk/CairoGraphics2D.java
+ (drawString(float,float)): Use text layout cache from
+ GdkFontPeer.
+ (getFontMetrics): Delegate to GdkFontPeer.
+ * gnu/java/awt/peer/gtk/FreetypeGlyphVector.java
+ (getGlyphCodes): Also check array size.
+ (getGlyphPositions): Also check array size.
+ * gnu/java/awt/peer/gtk/GdkFontPeer.java
+ (GdkFontLineMetrics.fm): Removed.
+ (GdkFontLineMetrics.strikeThroughOffset): Removed.
+ (GdkFontLineMetrics.strikeThroughThickness): Removed.
+ (GdkFontLineMetrics.underlineOffset): Removed.
+ (GdkFontLineMetrics.underlineThickness): Removed.
+ (GdkFontLineMetrics.GdkFontLineMetrics): Don't take
+ FontMetrics argument. Don't init removed fields.
+ (GdkFontLineMetrics.getAscent): Return font peer's field.
+ (GdkFontLineMetrics.getDescent): Return font peer's field.
+ (GdkFontLineMetrics.getHeight): Return font peer's field.
+ (GdkFontLineMetrics.getLeading): Return font peer's field.
+ (GdkFontLineMetrics.getNumChars): Reformat.
+ (GdkFontLineMetrics.getStrikeThroughOffset): Return half ascent.
+ (GdkFontLineMetrics.getStrikeThroughThickness): Return 1.
+ (GdkFontLineMetrics.getUnderlineOffset): Return font peer's field.
+ (GdkFontLineMetrics.getUnderlineThickness): Return font peer's field.
+ (GdkFontMetrics): Moved class in here as inner class.
+ Make it use the font peer's fields and for the char(s) width
+ and string width method, use TextLayout to measure the actual widths.
+ (ascent): New field.
+ (bundle): Removed.
+ (DEFAULT_CTX): New constant field.
+ (descent): New field.
+ (FONT_METRICS_ASCENT): New constant.
+ (FONT_METRICS_DESCENT): New constant.
+ (FONT_METRICS_HEIGHT): New constant.
+ (FONT_METRICS_MAX_ADVANCE): New constant.
+ (FONT_METRICS_MAX_ASCENT): New constant.
+ (FONT_METRICS_MAX_DESCENT): New constant.
+ (FONT_METRICS_UNDERLINE_OFFSET): New constant.
+ (FONT_METRICS_UNDERLINE_THICKNESS): New constant.
+ (height): New field.
+ (maxAdvance): New field.
+ (maxAscent): New field.
+ (maxDescent): New field.
+ (metrics): New field. Stores a FontMetrics for this font.
+ (textLayoutCache): New field. Caches TextLayout instances.
+ (underlineOffset): New field.
+ (underlineThickness): New field.
+ (cinit): Don't initialize resource bundle.
+ (GdkFontPeer): Setup the metrics.
+ (getFontMetrics): Return stored metrics if possible.
+ (getLineMetrics): Adapt to new constructor.
+ (initFont): New helper method.
+ (setupMetrics): New helper method.
+ * gnu/java/awt/peer/gtk/GtkToolkit.java
+ (LRUCache): Made class a static class.
+ (getFontMetrics): Delegate to GdkFontPeer.
+ * native/jni/gtk-peer/gdkfont.h
+ Added new constant defines.
+ * native/jni/gtk-peer/gnu_java_awt_peer_gtk_GdkFontPeer.c
+ (getFontMetrics): Rewritten to fetch the font metrics from
+ FreeType.
+
+2006-11-20 Tania Bento <tbento@redhat.com>
+
+ * javax/swing/ButtonGroup.java:
+ (setSelected): Select the ButtonModel if all conditions
+ are met.
+
+2006-11-20 Tania Bento <tbento@redhat.com>
+
+ * javax/swing/JSlider.java:
+ (updateLabelUIs): Removed casting.
+
+2006-11-20 Mark Wielaard <mark@klomp.org>
+
+ * gnu/java/util/regex/RE.java (messages): Don't initialize.
+ (bundle): New static final String field.
+ (getLocalizedMessage): Initialize messages when still null.
+ * gnu/java/util/regex/RESyntax.java (SYNTAX_IS_FINAL): Removed.
+ (set): Use RE.getLocalizedMessage().
+ (clear): Likewise.
+ (setLineSeparator): Likewise.
+
+2006-11-20 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/html/StyleSheet.java
+ (linked): New field.
+ (styleSheet): Replaced by linked.
+ (addStyleSheet): Use an arraylist for simplicity.
+ (getRule): Removed useless instantiation.
+ (getStyleSheets): Convert array list to array.
+ (removeStyleSheet): Use an arraylist for simplicity.
+ (resolveStyle): Include styles from linked lists.
+
+2006-11-20 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/BoxView.java
+ (BoxView): Initialize with invalid req's.
+ (forwardUpdate): Trigger repaint when children changed the
+ major axis.
+ (getResizeWeight): Return resizable when the pref differs from
+ the min or the pref differs from the max size.
+ (layoutMajorAxis): Actually sum up the preferred sizes.
+ (paint): Made binary search more robust.
+ (replace): Let arrays shrink when needed.
+ (replaceLayoutArray): Let arrays shrink when needed.
+ (setAxis): Trigger preferenceChanged.
+ * javax/swing/text/CompositeView.java
+ (getInsideAllocation): Call insets method to take account
+ of overriding subclasses.
+ * javax/swing/text/DefaultStyledDocument.java
+ (ElementBuffer.finishEdit): Clear the stack and edits buffer.
+ (ElementBuffer.insertUpdate): Only remove the found element, not
+ all.
+ * javax/swing/text/GlyphView.java
+ (insertUpdate): Pass null in preferenceChanged.
+ (removeUpdate): Pass null in preferenceChanged.
+ (changedUpdate): Pass null in preferenceChanged.
+ * javax/swing/text/Utilities.java
+ (drawTabbedText): Avoid single calls to charWidth() and instead
+ call charsWidth() on whole chunks.
+ * javax/swing/text/html/HTMLDocument.java
+ (BlockElement.getName): Fall back to super when necessary.
+ (RunElement.getName): Fall back to super when necessary.
+ (HTMLReader.MAX_THRESHOLD): New constant field.
+ (HTMLReader.GROW_THRESHOLD): New constant field.
+ (HTMLReader.theshold): New field.
+ (HTMLReader.HTMLReader): Fetch threshold from document.
+ (HTMLReader.addContent): Sucessivly grow the threshold.
+ (createLeafElement): Don't create two elemens and don't set
+ attribute.
+ * javax/swing/text/html/TableView.java
+ (RowView.replace): Invalidate grid.
+ (gridValid): Made package private.
+ (layoutMinorAxis): Mark all rows as invalid.
+ (replace): Invalidate grid.
+
+2006-11-20 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/AbstractDocument.java
+ (DefaultDocumentEvent.changes): Changed to be a HashMap.
+ (DefaultDocumentEvent.modified): Made private.
+ (DefaultDocumentEvent.THRESHOLD): New constant field.
+ (DefaultDocumentEvent.DefaultDocumentEvent): Don't initialize
+ changes table.
+ (DefaultDocumentEvent.addEdit): Switch to hashmap only when
+ exceeding threshold.
+ (DefaultDocumentEvent.getChange): Use iterative approach
+ when we have no hashmap yet.
+ (documentCV): Removed.
+ (numWriters): Renamed from numWritersWaiting.
+ (createPosition): Reformat.
+ (getCurrentWriter): Synchronized.
+ (readLock): Implement more straightforward.
+ (readUnlock): Implement more straightforward.
+ (writeLock): Implement more straightforward.
+ (writeUnlock): Implement more straightforward.
+ (remove): Write-lock here.
+ (removeImpl): Don't write-lock here.
+
+2006-11-20 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/JEditorPane.java
+ (setPage): Set priority on loading thread.
+
+2006-11-20 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/plaf/basic/BasicTextUI.java
+ (RootView.paint): Avoid allocation.
+ (cachedInsets): New field. Caches an Insets instance.
+ (getNextVisualPositionFrom): Read-lock the document to avoid
+ thread nastiness. Push allocation.
+ (getPreferredSize): Push fake allocation when not yet laid out.
+ (getVisibleEditorRect): Use cached insets.
+ (viewToModel): Read-lock the document to avoid
+ thread nastiness. Push allocation.
+
+2006-11-20 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/StyleContext.java
+ (attributeSetPool): Synchronize this map.
+ (addAttribute): Synchronize this method.
+ (addAttributes: Synchronize this method.
+ (readObject): Install synchronized map on target object.
+ (removeAttribute): Synchronize this method.
+ (removeAttributes): Synchronize this method.
+ (removeAttributes): Synchronize this method.
+
+2006-11-20 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/GapContent.java
+ (GapContentPosition.GapContentPosition): Removed constructor.
+ (Mark): Made subclass of WeakReference to refer directly to
+ the associated position.
+ (Mark.refCount): Removed.
+ (Mark.Mark(int,GapContentPosition,ReferenceQueue):
+ New constructor. Used to reference a position and register the
+ reference queue.
+ (Mark.Mark(index)): Call super and don't adjust mark offset.
+ (Mark.compareTo): Removed.
+ (Mark.equals): Removed.
+ (Mark.getOffset): Return at least null. Removed assert.
+ (Mark.getPosition): New helper method.
+ (garbageMarks): New field.
+ (positions): Removed.
+ (searchMark): New field.
+ (GapContent): Removed init of positions map.
+ (addImpl): New helper method.
+ (adjustPositionsInRange): Removed.
+ (compare): New helper method.
+ (createPosition): Rewritten for new datastructures. This now
+ performs a much more efficient binary search for finding
+ a position at the requested offste.
+ (garbageCollect): Rewritten to collect unused marks.
+ (getPositionsInRange): Adjusted for new data structures.
+ (removeImpl): New helper method.
+ (replace): Use new addImpl() and removeImpl() helper method for
+ correctly adjusting the positions and gap.
+ (search): Rewritten. Implements a more suitable binary search.
+ (searchFirst): New helper method.
+ (setPositionsInRange): Removed.
+ (shiftEnd): Update the marks here.
+ (shiftGap): Update the marks here.
+ (shiftGapEndUp): Update the marks here.
+ (shiftGapStartDown): Update the marks here.
+
+2006-11-20 Marco Trudel <mtrudel@gmx.ch>
+
+ * java/util/jar/JarFile.java (digestAlgorithms): New field used to cache
+ digest algorithm implementations.
+ (readSignatures): Parse the manifest once and reuse that data.
+ Add support for line breaks.
+ (verifyHashes): Use the parsed manifest entry.
+ (readManifestEntry): Removed.
+
+2006-11-20 Andrew John Hughes <gnu_andrew@member.fsf.org>
+
+ * java/beans/beancontext/BeanContextServicesSupport.java:
+ Added more documentation.
+ (addService(Class,BeanContextServiceProvider,boolean)):
+ Synchronized over global hierarchy lock.
+ (bcsPreDeserializationHook(ObjectInputStream)): Implemented.
+ (bcsPreSerializationHook(ObjectOutputStream)): Implemented.
+ (childJustRemovedHook(Object,BCSChild)): Implemented.
+ (createBCSSServiceProvider(Class,BeanContextServiceProvider)):
+ Implemented.
+ (fireServiceRevoked(BeanContextServiceRevokedEvent)):
+ Added revocation-only listeners.
+ (getBeanContextServicesPeer()): Implemented.
+ (getCurrentServiceClasses(Class)): Synchronized over global
+ hierarchy lock.
+ (getCurrentServiceSelectors(Class)): Synchronized over global
+ hierarchy lock, and fixed FIXME.
+ (getService(BeanContextChild,Object,Class,Object,
+ BeanContextServiceRevokedListener)): Implemented.
+ (hasService(Class)): Synchronized over global hierarchy lock.
+ (releaseService(BeanContextChild,Object,Object)): Implemented.
+ (revokeService(Class,BeanContextServiceProvider,boolean)): Implemented.
+ * java/beans/beancontext/BeanContextSupport.java:
+ (remove(Object, boolean)): Documentation correction.
+
+2006-11-19 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/JEditorPane.java
+ (PageStream): New inner class.
+ (PageLoader): New inner class.
+ (loading): New field.
+ (setPage): Implemented asynchronous loading.
+ * javax/swing/text/DefaultStyledDocument.java
+ (ElementBuffer.create): New helper method.
+ (create): Use new ElementBuffer method instead of hack.
+ * javax/swing/text/html/HTMLDocument.java
+ (HTMLReader.flushImpl): New helper method.
+ (HTMLReader.addContent): Use flushImpl().
+ (HTMLReader.blockClose): Added null check.
+ (HTMLReader.flush): Use flushImpl().
+ * javax/swing/text/html/HTMLEditorKit.java
+ (createDefaultDocument): Set load priority to 4 and token threshold
+ to 100.
+ * javax/swing/text/html/TableView.java
+ (insertUpdate): Overridden to provide correct view factory.
+ (removeUpdate): Overridden to provide correct view factory.
+ (changedUpdate): Overridden to provide correct view factory.
+
+2006-11-19 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/BoxView.java
+ (clipRect): New field.
+ (tmpRect): New field.
+ (layout): Reorganized code. Now uses layoutAxis() helper method.
+ (layoutAxis): New helper method.
+ (paint): Optimized by using cached Rectangle objects and
+ a binary search for child views inside the clip.
+ * javax/swing/text/CompositeView.java
+ (insideAllocation): Made private and initialized in constructor.
+ (getInsideAllocation): Removed initialization block for
+ insideAllocation field. Avoid unnecessary allocations.
+ * javax/swing/text/GlyphView.java
+ (DefaultGlyphPainter.paint): Only paint the actual glyphs here
+ The remaining stuff (background, underline and striking) is
+ done in the GlpyhView itself. Avoid unnecessary allocations.
+ (cached): A cached Segment instance.
+ (getText): Return cached segment.
+ (paint): Paint underline, strike and background here. Avoid
+ unecessary allocs.
+
+2006-11-19 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/html/StyleSheet.java
+ (getFontSize): Removed debug output.
+ (ListPainter.tmpRect): New field.
+ (ListPainter.paint): Align bullet vertically centered to
+ the first line of the paragraph.
+
+2006-11-17 Roman Kennke <kennke@aicas.com>
+
+ * gnu/javax/swing/text/html/css/CSSParser.java
+ (parseDeclaration): Trim string before reporting.
+ * gnu/javax/swing/text/html/css/FontSize.java
+ (size): New field.
+ (isRelative): New field.
+ (sizeIndex): New field.
+ (FontSize): Initialize new fields.
+ (getValue): Changed to call getValue(int).
+ (getValue(int)): New method. Implements relative font sizes.
+ (isRelative): New method.
+ (mapAbsolute): Store index.
+ (mapEM): New helper method.
+ (mapLarger): New helper method.
+ (mapPercent): New helper method.
+ (mapRelative): New helper method.
+ (mapSmaller): New helper method.
+ (mapValue): New helper method.
+ * javax/swing/text/html/CSS.java
+ (parseBackgroundShorthand): Create CSSColor directly.
+ * javax/swing/text/html/StyleSheet.java
+ (addRule): Invalidate resolved styles.
+ (getFont): Call new getFontSize() method to resolve relative
+ font sizes.
+ (getFontSize): New helper method. Resolves relative font sizes.
+ (translateHTMLToCSS): Create CSS objects directly.
+
2006-11-13 Andrew John Hughes <gnu_andrew@member.fsf.org>
* gnu/java/util/regex/RETokenNamedProperty.java:
(getHandler(String)): Add support for 'all'.
+
+2006-11-18 Andrew John Hughes <gnu_andrew@member.fsf.org>
+
+ * gnu/javax/management/Server.java:
+ Initial implementation of a GNU management server.
+ * javax/management/MBeanPermission.java,
+ * javax/management/MBeanRegistration.java,
+ * javax/management/MBeanTrustPermission.java:
+ Implemented.
+2006-11-17 Mark Wielaard <mark@klomp.org>
+
+ * docs/www.gnu.org/newsitems.txt: Add Sun GPL news announcement.
+
+2006-11-17 Gary Benson <gbenson@redhat.com>
+
+ * java/net/DatagramSocket.java (getLocalAddress, connect,
+ receive): Perform security check on address not hostname.
+
+2006-11-16 Roman Kennke <kennke@aicas.com>
+
+ * gnu/javax/swing/text/html/parser/support/Parser.java
+ (_handleText): Fixed condition for consuming whitespace.
+ Removed validator check, this is superfluous now.
+
+2006-11-16 Roman Kennke <kennke@aicas.com>
+
+ * gnu/javax/swing/text/html/css/CSSParser.java
+ (parseRuleset): Support 'combined' selectors.
+ (main): Adapt callback for combined selectors support.
+ * gnu/javax/swing/text/html/css/CSSParserCallback.java
+ (startStatement): Take selector array as argument, to
+ support combined selectors.
+ * javax/swing/text/html/BlockView.java
+ (calculateMinorAxisRequirements): Fetch and apply alignment.
+ * javax/swing/text/html/StyleSheet.java
+ (CSSStyle): Inverted the constants for correct precedence.
+ (CSSStyleSheetParserCallback.styles): New field. Stores the current
+ styles.
+ (CSSStyleSheetParserCallback.style): Removed.
+ (CSSStyleSheetParserCallback.declaration): Update multiple styles.
+ (CSSStyleSheetParserCallback.end): Push multiple styles.
+ (CSSStyleSheetParserCallback.start): Initialize multiple styles.
+
+2006-11-16 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/FlowView.java
+ (LogicalView.getPreferredSpan): Calculate maximum correctly.
+ * javax/swing/text/GlyphView.java
+ (tabExpander): New field.
+ (tabX): New field.
+ (breakView): Set tabX on broken view.
+ (getPartialSpan): Let the painter fetch the span.
+ (getTabbedSpan): Update the tab expander field. Maybe trigger
+ relayout.
+ (getTabExpander): Simply return the stored expander.
+ * javax/swing/text/Utilities.java
+ (getTabbedTextOffset): Made algoritm a little smarter and more
+ efficient.
+ (getTabbedTextWidth): Don't add single char widths, instead add
+ chunks of characters.
+ * javax/swing/text/html/ParagraphView.java
+ (calculateMinorAxisRequirements): Adjust margin only when the
+ CSS span is not fixed.
+
+2006-11-16 David Gilbert <david.gilbert@object-refinery.com>
+
+ * java/beans/beancontext/BeanContextSupport.java
+ (getChildBeanContextMembershipListener): Implemented,
+ (getChildPropertyChangeListener): Implemented,
+ (getChildSerializable): Implemented,
+ (getChildVetoableChangeListener): Implemented,
+ (getChildVisibility): Implemented,
+ (setDesignTime): Use same property name as Sun's implementation.
+
+2006-11-16 David Gilbert <david.gilbert@object-refinery.com>
+
+ * java/beans/DesignMode.java: Reformatted and removed a FIXME,
+ * java/beans/Statement.java
+ (toString): Updated to match reference implementation.
+
+2006-11-15 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/html/HTMLEditorKit.java
+ (InsertHTMLTextAction.actionPerformed): Also try inserting
+ the alternate tag. Adjust the selection accordingly.
+ (InsertHTMLTextAction.adjustSelection): New helper method.
+ Adjusts the selection after an insertion.
+ (insertAtBoundary): Delegate to deprecated method.
+ (insertAtBoundry): Implemented missing method.
+ (tryInsert): New helper method.
+ (defaultActions): Implemented to fill the array with
+ a couple of InsertHTMLTextActions.
+
+2006-11-15 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/html/ImageView.java
+ (Observer): New class. Observes image loading.
+ (haveHeight): New field.
+ (haveWidth): New field.
+ (height): New field.
+ (width): New field.
+ (image): New field.
+ (imageIcon): New field.
+ (loading): New field.
+ (observer): New field.
+ (reloadImage): New field.
+ (reloadProperties): New field.
+ (ImageView): Initialize observer and some flags.
+ (getImage): Update the image state and return the image.
+ (loadImage): New helper method. Actually starts loading.
+ (paint): Rewritten to paint the image directly, not via Icon.
+ (reloadImage): Rewritten. Loads the image and its properties.
+ (renderIcon): Removed. No more necessary.
+ (setPropertiesFromAttributes): Don't nullify image here.
+ Added comment about missing impl.
+ (setSize): Added comment about missing impl.
+ (updateSize): New helper method. Updates the size attributes.
+ (updateState): New helper method. Makes sure the image
+ and its properties are valid.
+
+2006-11-15 Roman Kennke <kennke@aicas.com>
+
+ * gnu/javax/swing/text/html/parser/support/Parser.java
+ (_handleEndTag_remaining): Consume whitespace after a closing
+ block like tag.
+
+2006-11-15 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/html/HTMLDocument.java
+ (HTMLReader.ParagraphAction.end): Call super instead of blockClose()
+ directly.
+ (HTMLReader.ParagraphAction.start): Call super instead of blockOpen()
+ directly.
+ (HTMLReader.parseStack): Removed.
+ (HTMLReader.blockClose): Simply call addContent() with ' '
+ instead of doing more complicated stuff. Removed parseStack
+ handling.
+ (HTMLReader.blockOpen): Removed parseStack handling.
+ (getInsertingReader): Removed parseStack init.
+ * gnu/javax/swing/text/html/parser/htmlValidator.java
+ (closeTag): Return true only when the tag actually should be
+ closed.
+ * gnu/javax/swing/text/html/parser/support/Parser.java
+ (_handleEndTag): Only actually close the tag when the validator
+ allows it.
+
+2006-11-15 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/html/CSS.java
+ (Attribute.BORDER_SPACING): New field for the CSS border-spacing
+ attribute.
+ * javax/swing/text/html/StyleSheet.java
+ (BoxPainter.bottomPadding): New field.
+ (BoxPainter.leftPadding): New field.
+ (BoxPainter.rightPadding): New field.
+ (BoxPainter.topPadding): New field.
+ (BoxPainter.BoxPainter): Fetch the MARGIN and PADDING* attributes
+ too.
+ (BoxPainter.getInset): Recognize and include the padding.
+ (translateHTMLToCSS): Added mapping for CELLPADDING and CELLSPACING.
+ javax/swing/text/html/TableView.java
+ (RowView.calculateMajorAxisRequirements): Adjust req's for
+ cellSpacing.
+ (RowView.layoutMajorAxis): Adjust multi-column span for cellSpacing.
+ (cellSpacing): New field.
+ (columnRequirements): Made package private to avoid accessor method.
+ (calculateMinorAxisRequirements): Include cellSpacing.
+ (calculateMajorAxisRequirements): Overridden to include cellSpacing.
+ (layoutMajorAxis): Likewise.
+ (layoutColumns): Respect cellSpacing.
+ (setParent): Overridden to fetch the CSS attributes when view gets
+ connected.
+ (setPropertiesFromAttributes): New method. Fetches the cell
+ spacing from the CSS attributes.
+
+2006-11-15 Roman Kennke <kennke@aicas.com>
+
+ * gnu/javax/swing/text/html/parser/support/Parser.java
+ (_handleText): Consume whitespace directly before a closing tag.
+ (restOfTag): Consume whitespace directly after opening.
+ * gnu/javax/swing/text/html/parser/support/textPreProcessor.java
+ (preprocess): Don't perform array boundary checking by
+ catch AIOOBE, instead check the boundary in loop condition.
+ * gnu/javax/swing/text/html/parser/support/low/Constants.java
+ (TAG_CLOSE): New constants. Describes the token pattern for
+ a closing tag.
+
+2006-11-14 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/text/html/ImageView.java
+ (getPreferredSpan): Use CSS length values.
+ * javax/swing/text/html/TableView.java
+ (CellView.calculateMajorAxisRequirements): Overridden to
+ set the maximum reqs to maximum.
+ (RowView.getMaximumSize): For the X_AXIS, set the maximum
+ span to maximum.
+ (RowView.getMinimumSpan): Overridden. For the X_AXIS, return
+ the total column reqs.
+ (RowView.getPreferredSpan): Overridden. For the X_AXIS, return
+ the total column reqs.
+ * gnu/javax/swing/text/html/css/CSSColor.java
+ (convertValue): Catch NumberFormatExceptions for more robustness.
+ * gnu/javax/swing/text/html/css/FontSize.java
+ (mapPixels): Actually map px values. Catch NFE for more robustness.
+
+2006-11-14 Roman Kennke <kennke@aicas.com>
+
+ * gnu/java/awt/font/autofit/AxisHints.java,
+ * gnu/java/awt/font/autofit/Constants.java,
+ * gnu/java/awt/font/autofit/GlyphHints.java,
+ * nu/java/awt/font/autofit/Latin.java,
+ * nu/java/awt/font/autofit/LatinAxis.java,
+ * gnu/java/awt/font/autofit/LatinMetrics.java,
+ * gnu/java/awt/font/autofit/Scaler.java,
+ * gnu/java/awt/font/autofit/Script.java,
+ * gnu/java/awt/font/autofit/ScriptMetrics.java,
+ * gnu/java/awt/font/autofit/Segment.java,
+ * gnu/java/awt/font/autofit/Width.java:
+ New classes. This is some skeleton stuff for the FreeType-alike
+ auto-gridfitter.
+ * gnu/java/awt/font/opentype/CharGlyphMap.java: Made class public.
+ * gnu/java/awt/font/opentype/OpenTypeFont.java
+ (unitsPerEm): Made field public.
+ (getRawGlyphOutline): New method. Fetches the raw outline.
+ * gnu/java/awt/font/opentype/Scaler.java
+ (getRawGlyphOutline): New method. Fetches the raw outline.
+ * gnu/java/awt/font/opentype/truetype/GlyphLoader.java
+ (loadGlyph): New method. This is used to load raw outlines.
+ * gnu/java/awt/font/opentype/truetype/TrueTypeScaler.java
+ (getRawOutline): New method. Fetches the raw outline.
+ * gnu/java/awt/font/opentype/truetype/Zone.java:
+ Made class public.
+
+2006-11-14 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/RepaintManager.java
+ (RepaintManager): Fetch the default state for the double buffering
+ from a system property gnu.swing.doublebuffering.
+
+2006-11-14 Roman Kennke <kennke@aicas.com>
+
+ * javax/swing/plaf/basic/BasicLabelUI.java
+ (cachedInsets): New field.
+ (getFontMetrics): New helper method. Fetches the font metrics
+ from the component or the toolkit.
+ (getPreferredSize): Use getFontMetrics() helper method for
+ fetching the font metrics.
+ (paint): Use getFontMetrics() helper method for
+ fetching the font metrics. Only paint if icon or text
+ are != null. Use cached insets.
+ (paintDisabledText): Don't store/restore color object. The
+ JComponent painting mechanism takes care of this by calling
+ create().
+ (paintEnabledText): Don't store/restore color object. The
+ JComponent painting mechanism takes care of this by calling
+ create().
+
+2006-11-14 Roman Kennke <kennke@aicas.com>
+
+ * gnu/java/awt/peer/GLightweightPeer.java
+ (handleEvent): Try to do something reasonable and trigger painting
+ for the lightweight component.
+ (getFontMetrics): Fetch and return a font metrics object from
+ the Toolkit.
+
+2006-11-13 Andrew John Hughes <gnu_andrew@member.fsf.org>
+
+ * gnu/java/util/regex/RETokenNamedProperty.java:
+ (getHandler(String)): Add support for 'all'.
+
+2006-11-13 Andreas Tobler <a.tobler@schweiz.org>
+
+ * AUTHORS: Add myself.
+
+2006-11-13 Thomas Fitzsimmons <fitzsim@redhat.com>
+
+ * AUTHORS: Add Tania Bento, Thomas Fitzsimmons, Francis Kung and
+ Dalibor Topic. Re-order 'K' section. Remove trailing space from
+ Roman Kennke's entry.
+
+2006-11-13 Roman Kennke <kennke@aicas.com>
+
+ * java/awt/image/IndexColorModel.java
+ (createCompatibleSampleModel): Implemented missing method.
+
2006-11-11 Andreas Tobler <a.tobler@schweiz.org>
* gnu/java/awt/peer/gtk/GtkImageConsumer.java (setPixels): Handle data
diff --git a/configure.ac b/configure.ac
index c1fb86c8c..f79dbc452 100644
--- a/configure.ac
+++ b/configure.ac
@@ -357,7 +357,8 @@ if test "x${COMPILE_JNI}" = xyes; then
fcntl.h \
sys/mman.h \
magic.h \
- sys/event.h sys/epoll.h])
+ sys/event.h sys/epoll.h \
+ ifaddrs.h])
AC_EGREP_HEADER(uint32_t, stdint.h, AC_DEFINE(HAVE_INT32_DEFINED, 1, [Define to 1 if you have uint32_t]))
AC_EGREP_HEADER(uint32_t, inttypes.h, AC_DEFINE(HAVE_INT32_DEFINED, 1, [Define to 1 if you have uint32_t]))
diff --git a/doc/api/Makefile.am b/doc/api/Makefile.am
index 96c586e07..b63b31f34 100644
--- a/doc/api/Makefile.am
+++ b/doc/api/Makefile.am
@@ -36,6 +36,7 @@ clean-local:
create_html:
-$(MKDIR) html > /dev/null 2>&1
+if CREATE_API_DOCS
$(GJDOC) \
-use \
-sourcepath "$(sourcepath)" \
@@ -51,3 +52,4 @@ create_html:
-header $(classpathbox) -footer $(classpathbox) \
-subpackages java:javax:org
touch create_html
+endif
diff --git a/doc/vmintegration.texinfo b/doc/vmintegration.texinfo
index e7f85d088..6d59b5d8f 100644
--- a/doc/vmintegration.texinfo
+++ b/doc/vmintegration.texinfo
@@ -144,7 +144,7 @@ A simple, small bytecode interpreter that works out-of-the-box with
pure GNU Classpath; it is emerging as the preferred platform for
quickly testing a new build of GNU Classpath. Licensed under the GPL.
-@item @uref{http://oss.software.ibm.com/jikesrvm,Jikes RVM}
+@item @uref{http://jikesrvm.sourceforge.net/,Jikes RVM}
A free runtime environment for Java, written in Java. Works
out-of-the-box with pure GNU Classpath. Features an optimizing JIT.
Runs on the x86 and PowerPC architectures, on the AIX, Linux, and Mac
@@ -1859,8 +1859,7 @@ Classpath places a few requirements on the VM that uses it.
Classpath currently uses only JNI 1.1, except for one JNI 1.2 function
in the JNI Invocation API: GetEnv(). And GetEnv() is only used in the
-``portable native sync'' code, so it's only actually used by Jikes RVM
-and Kaffe.
+now deprecated ``portable native sync'' code.
A future direction will probably be to require that all VMs provide
JNI 1.2. If this poses problems, please raise them on the classpath
@@ -1870,42 +1869,95 @@ mailing list.
@comment node-name, next, previous, up
@section VM Threading Model
-Classpath's AWT peers use GTK+. GTK+ uses GLIB. Normally, Classpath
-will initialize GLIB's @dfn{gthreads} to use
-the platform's native threading model@footnote{The native threading
-model is pthreads on Linux and AIX, the two platforms Classpath
-currently runs on.}
-
-If the Java runtime doesn't use the native threading model, then you
-will want Classpath to tell GLIB to use the Java threading primitives
-instead. Otherwise, GLIB would use the native threading model to
-perform operations such as creating thread-local data, and that just
-doesn't work on systems (such as Kaffe in some configurations, and
-such as Jikes RVM) that use @i{m}:@i{n} threading.
-
-Historically, enabling the Java threading primitives had been done at
-build time, by configuring classpath with the
-@option{--portable-native-sync} option. This had bad consequences,
-though -- it meant that the prebuild GNU Classpath package distributed
-with Debian GNU/Linux would not be usable with VMs that could
-otherwise have used it. Instead, we encourage
-the use of the Java system property
-@code{gnu.classpath.awt.gtk.portable.native.sync}. A VM that wants
-GLIB to use the Java threading primitives should modify
-@code{VMRuntime.insertSystemProperties()} to include code like the
-following:
-
-@example
-static void insertSystemProperties(Properties @var{p})
-@end example
-...
-@example
-@var{p}.put("gnu.classpath.awt.gtk.portable.native.sync", "true");
-@end example
-
-So, the configure option
-@option{--portable-native-sync} is deprecated, and should go away in a
-subsequent release of GNU Classpath.
+VM authors can implement a number of different threading models. When
+native code is also threaded there is the potential for one threading
+model to deadlock the other. The
+@uref{http://java.sun.com/docs/books/jni/html/other.html#29406,Java
+Native Interface Programmer's Guide and Specification} suggests
+consulting VM documentation in such situations. Classpath uses
+existing libraries, for example the AWT peers can use the GTK+
+graphics library. As these libraries assume a different threading
+model, there is the potential for the native code to deadlock a VM.
+
+The different threading models available to a VM author are:
+@enumerate
+@item
+@i{Native threads}: Map a Java thread to an underlying operating system
+thread (normally a POSIX compatible pthread). This approach reduces
+the potential for deadlock as there is only one thread scheduling
+mechanism.
+@item
+@i{Green threads 1}: Green threads are threads scheduled by the VM,
+typically by switching swapping registers. In early VMs green threads
+were seen as advantageous as they didn't require the operating system
+to resechedule, save and swap all of a threads registers. The green
+thread 1 model switches thread on an externally created event, such as
+a timer interrupt. An example of a VM using this approach is Kaffe
+configured with its jthreads model.
+@item
+@i{Green threads 2}: The essential difference with this model is to
+not switch threads on an event, but at fixed points in the code being
+executed by the VM. Points chosen could be backward branches (loops)
+or method calls. This approach can be advantageous to nonconservative
+garbage collectors, as non-running threads would be at known points
+and can have fixed register maps. It can also reduce the number of
+registers it is necessary to swap when switching threads.
+@item
+@i{M:N threading}: a flaw to green threading is that it is unable to
+use multiple processors. @i{M}:@i{N} threading fixes this problem by
+running groups of green threads on multiple underlying native
+threads. An example of a VM using this approach is the Jikes RVM,
+which uses @i{M}:@i{N} threading combined with the green thread 2
+model.
+@end enumerate
+
+An example of the problem of mixing threading models is:
+@itemize @bullet
+@item
+A Java thread calls a native method. The native method aquires a lock.
+@item
+The native method calls back into the VM.
+@item
+An event triggers the VM to reschedule the currently running thread.
+@item
+A new VM thread, executing on the same underlying native thread, calls
+a native method.
+@item
+The native method tries to aquire the lock already aquired earlier. As
+the lock is busy the thread waits and allows the operating system to
+reschedule native threads.
+@item
+The operating system reschedules the VM thread again, but the lock is
+still busy and in some threading models will remain busy forever
+(the VM is deadlocked).
+@end itemize
+
+VMs that don't use the underlying operating system thread scheduling
+mechanism need to avoid deadlock. One now deprecated approach was to
+build Classpath and VMs on top of a wrapper thread library (aka
+portable native sync). The wrapper thread library used was GLIB's
+@dfn{gthreads}. This approach has been deprecated because:
+@enumerate
+@item
+The wrapper library is only in use by some native libraries. For
+example, GTK+ uses the gthread library but QT does not.
+@item
+The wrapper library can't be in use prior to the VM starting as the VM
+must replace the wrapper libraries functions with its own. This
+prevents the VM from running as a plugin in an application that
+already uses the wrapper library.
+@end enumerate
+
+An alternative approach is for the VM to detect deadlocked native code
+and swap Java threads off of that native thread. The VM can't,
+however, swap two blocked native threads that are potentially
+deadlocking each other on a lock. The lock will be associated with the
+native thread. To prevent this from happening the VM must hijack
+functions that operate on locks. This is done by redifining the lock
+functions inside the VM and configuring the linker so that it uses the
+VMs symbol in preference to that of the external thread support
+library. The VM's lock function can then reschedule Java threads if it
+must wait for the lock.
@node Boot Library Path Property, , VM Threading Model, Miscellaneous VM Requirements
@comment node-name, next, previous, up
diff --git a/doc/www.gnu.org/newsitems.txt b/doc/www.gnu.org/newsitems.txt
index 5d5184ac4..8e37aa9df 100644
--- a/doc/www.gnu.org/newsitems.txt
+++ b/doc/www.gnu.org/newsitems.txt
@@ -1,3 +1,10 @@
+<newsitem date="15 Nov 2006">
+<createlink name="FSF welcomes Sun's GPL release of Java"
+ url="http://www.fsf.org/news/fsf-welcomes-gpl-java.html"><br>
+<createlink name="Planet Classpath" url="http://planet.classpath.org/">
+carries the reactions of many individual GNU Classpath developers.
+</newsitem>
+
<newsitem date="09 Aug 2006">
<createlink name="GNU Classpath 0.92"
url="announce/20060809.html">
diff --git a/gnu/java/awt/font/autofit/AxisHints.java b/gnu/java/awt/font/autofit/AxisHints.java
new file mode 100644
index 000000000..b2c991234
--- /dev/null
+++ b/gnu/java/awt/font/autofit/AxisHints.java
@@ -0,0 +1,45 @@
+/* AxisHints.java -- FIXME: briefly describe file purpose
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+class AxisHints
+{
+
+ Segment[] segments;
+}
diff --git a/gnu/java/awt/font/autofit/Constants.java b/gnu/java/awt/font/autofit/Constants.java
new file mode 100644
index 000000000..cb3992825
--- /dev/null
+++ b/gnu/java/awt/font/autofit/Constants.java
@@ -0,0 +1,61 @@
+/* Constants.java -- Some constants used in the autofitter
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+/**
+ * Some constants used in the autofitter.
+ */
+interface Constants
+{
+
+ /**
+ * The horizontal dimension.
+ */
+ static final int DIMENSION_HORZ = 0;
+
+ /**
+ * The vertical dimension.
+ */
+ static final int DIMENSION_VERT = 1;
+
+ /**
+ * The number of dimensions.
+ */
+ static final int DIMENSION_MAX = 2;
+}
diff --git a/gnu/java/awt/font/autofit/GlyphHints.java b/gnu/java/awt/font/autofit/GlyphHints.java
new file mode 100644
index 000000000..ad73a04a6
--- /dev/null
+++ b/gnu/java/awt/font/autofit/GlyphHints.java
@@ -0,0 +1,75 @@
+/* GlyphHints.java -- Data and methods for actual hinting
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+import gnu.java.awt.font.opentype.truetype.Zone;
+
+/**
+ * The data and methods used for the actual hinting process.
+ */
+class GlyphHints
+{
+
+ int xScale;
+ int xDelta;
+ int yScale;
+ int yDelta;
+
+ AxisHints[] axis;
+
+ void rescale(ScriptMetrics metrics)
+ {
+ // TODO: Implement.
+ }
+
+ void reload(Zone outline)
+ {
+ // TODO: Implement.
+ }
+
+ void computeSegments(int dim)
+ {
+ // TODO: Implement.
+ }
+
+ void linkSegments(int dim)
+ {
+ // TODO: Implement.
+ }
+}
diff --git a/gnu/java/awt/font/autofit/Latin.java b/gnu/java/awt/font/autofit/Latin.java
new file mode 100644
index 000000000..0352b41a4
--- /dev/null
+++ b/gnu/java/awt/font/autofit/Latin.java
@@ -0,0 +1,177 @@
+/* Latin.java -- Latin specific glyph handling
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+import java.awt.geom.AffineTransform;
+
+import gnu.java.awt.font.opentype.OpenTypeFont;
+import gnu.java.awt.font.opentype.truetype.Zone;
+
+/**
+ * Implements Latin specific glyph handling.
+ */
+class Latin
+ implements Script, Constants
+{
+
+ private static final int MAX_WIDTHS = 16;
+
+ public void applyHints(GlyphHints hints, ScriptMetrics metrics)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ public void doneMetrics(ScriptMetrics metrics)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ /**
+ * Initializes the <code>hints</code> object.
+ *
+ * @param hints the hints to initialize
+ * @param metrics the metrics to use
+ */
+ public void initHints(GlyphHints hints, ScriptMetrics metrics)
+ {
+ hints.rescale(metrics);
+ LatinMetrics lm = (LatinMetrics) metrics;
+ hints.xScale = lm.axis[DIMENSION_HORZ].scale;
+ hints.xDelta = lm.axis[DIMENSION_HORZ].delta;
+ hints.yScale = lm.axis[DIMENSION_VERT].scale;
+ hints.yDelta = lm.axis[DIMENSION_VERT].delta;
+ // TODO: Set the scaler and other flags.
+ }
+
+ /**
+ * Initializes the script metrics.
+ *
+ * @param metrics the script metrics to initialize
+ * @param face the font
+ */
+ public void initMetrics(ScriptMetrics metrics, OpenTypeFont face)
+ {
+ assert metrics instanceof LatinMetrics;
+ LatinMetrics lm = (LatinMetrics) metrics;
+ lm.unitsPerEm = face.unitsPerEm;
+
+ // TODO: Check for latin charmap.
+
+ initWidths(lm, face, 'o');
+ initBlues(lm, face);
+ }
+
+ public void scaleMetrics(ScriptMetrics metrics)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ /**
+ * Determines the standard stem widths.
+ *
+ * @param metrics the metrics to use
+ * @param face the font face
+ * @param ch the character that is used for getting the widths
+ */
+ private void initWidths(LatinMetrics metrics, OpenTypeFont face, char ch)
+ {
+ GlyphHints hints = new GlyphHints();
+ metrics.axis[DIMENSION_HORZ].widthCount = 0;
+ metrics.axis[DIMENSION_VERT].widthCount = 0;
+ int glyphIndex = face.getGlyph(ch);
+ // TODO: Avoid that AffineTransform constructor and change
+ // getRawGlyphOutline() to accept null or remove that parameter altogether.
+ // Consider this when the thing is done and we know what we need that for.
+ Zone outline = face.getRawGlyphOutline(glyphIndex, new AffineTransform());
+ LatinMetrics dummy = new LatinMetrics();
+ Scaler scaler = dummy.scaler;
+ dummy.unitsPerEm = metrics.unitsPerEm;
+ scaler.xScale = scaler.yScale = 10000;
+ scaler.xDelta = scaler.yDelta = 0;
+ scaler.face = face;
+ hints.rescale(dummy);
+ hints.reload(outline);
+ for (int dim = 0; dim < DIMENSION_MAX; dim++)
+ {
+ LatinAxis axis = metrics.axis[dim];
+ AxisHints axHints = hints.axis[dim];
+ int numWidths = 0;
+ hints.computeSegments(dim);
+ hints.linkSegments(dim);
+ Segment[] segs = axHints.segments;
+ for (int i = 0; i < segs.length; i++)
+ {
+ Segment seg = segs[i];
+ Segment link = seg.link;
+ if (link != null && link.link == seg && link.index > i)
+ {
+ int dist = Math.abs(seg.pos - link.pos);
+ if (numWidths < MAX_WIDTHS)
+ axis.widths[numWidths++].org = dist;
+ }
+ }
+ }
+ for (int dim = 0; dim < DIMENSION_MAX; dim++)
+ {
+ LatinAxis axis = metrics.axis[dim];
+ int stdw = axis.widthCount > 0 ? axis.widths[0].org
+ : constant(metrics, 50);
+ axis.edgeDistanceTreshold= stdw / 5;
+ }
+ }
+
+ /**
+ * Initializes the blue zones of the font.
+ *
+ * @param metrics the metrics to use
+ * @param face the font face to analyze
+ */
+ private void initBlues(LatinMetrics metrics, OpenTypeFont face)
+ {
+ // TODO: Implement.
+ }
+
+ private int constant(LatinMetrics metrics, int c)
+ {
+ return c * (metrics.unitsPerEm / 2048);
+ }
+}
diff --git a/gnu/java/awt/font/autofit/LatinAxis.java b/gnu/java/awt/font/autofit/LatinAxis.java
new file mode 100644
index 000000000..8ca1e6d9e
--- /dev/null
+++ b/gnu/java/awt/font/autofit/LatinAxis.java
@@ -0,0 +1,53 @@
+/* LatinAxis.java -- Axis specific data
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+/**
+ * Some axis specific data.
+ */
+class LatinAxis
+{
+
+ int scale;
+ int delta;
+
+ int widthCount;
+ Width[] widths;
+ float edgeDistanceTreshold;
+}
diff --git a/gnu/java/awt/font/autofit/LatinMetrics.java b/gnu/java/awt/font/autofit/LatinMetrics.java
new file mode 100644
index 000000000..cd955348b
--- /dev/null
+++ b/gnu/java/awt/font/autofit/LatinMetrics.java
@@ -0,0 +1,51 @@
+/* LatinMetrics.java -- Latin specific metrics data
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+/**
+ * Latin specific metrics data.
+ */
+class LatinMetrics
+ extends ScriptMetrics
+{
+
+ LatinAxis[] axis;
+
+ int unitsPerEm;
+}
diff --git a/gnu/java/awt/font/autofit/Scaler.java b/gnu/java/awt/font/autofit/Scaler.java
new file mode 100644
index 000000000..105185125
--- /dev/null
+++ b/gnu/java/awt/font/autofit/Scaler.java
@@ -0,0 +1,52 @@
+/* Scaler.java -- FIXME: briefly describe file purpose
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+import gnu.java.awt.font.opentype.OpenTypeFont;
+
+class Scaler
+{
+
+ int xScale;
+ int xDelta;
+ int yScale;
+ int yDelta;
+ OpenTypeFont face;
+
+}
diff --git a/gnu/java/awt/font/autofit/Script.java b/gnu/java/awt/font/autofit/Script.java
new file mode 100644
index 000000000..3b353010f
--- /dev/null
+++ b/gnu/java/awt/font/autofit/Script.java
@@ -0,0 +1,62 @@
+/* Script.java -- Defines script specific interface to the autofitter
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+import gnu.java.awt.font.opentype.OpenTypeFont;
+
+/**
+ * Defines script specific methods for the auto fitter.
+ */
+interface Script
+{
+
+ /**
+ * Initializes the metrics.
+ */
+ void initMetrics(ScriptMetrics metrics, OpenTypeFont face);
+
+ void scaleMetrics(ScriptMetrics metrics/* , scaler, map this */);
+
+ void doneMetrics(ScriptMetrics metrics);
+
+ void initHints(GlyphHints hints, ScriptMetrics metrics);
+
+ void applyHints(GlyphHints hints, /* some outline object, */
+ ScriptMetrics metrics);
+}
diff --git a/gnu/java/awt/font/autofit/ScriptMetrics.java b/gnu/java/awt/font/autofit/ScriptMetrics.java
new file mode 100644
index 000000000..77c815ae5
--- /dev/null
+++ b/gnu/java/awt/font/autofit/ScriptMetrics.java
@@ -0,0 +1,49 @@
+/* ScriptMetrics.java -- Script specific metrics data
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+/**
+ * Script specific metrics data.
+ */
+class ScriptMetrics
+{
+
+ Script script;
+ Scaler scaler;
+}
diff --git a/gnu/java/awt/font/autofit/Segment.java b/gnu/java/awt/font/autofit/Segment.java
new file mode 100644
index 000000000..32032a48f
--- /dev/null
+++ b/gnu/java/awt/font/autofit/Segment.java
@@ -0,0 +1,47 @@
+/* Segment.java -- FIXME: briefly describe file purpose
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+class Segment
+{
+
+ Segment link;
+ int index;
+ int pos;
+}
diff --git a/gnu/java/awt/font/autofit/Width.java b/gnu/java/awt/font/autofit/Width.java
new file mode 100644
index 000000000..d4d540069
--- /dev/null
+++ b/gnu/java/awt/font/autofit/Width.java
@@ -0,0 +1,46 @@
+/* Width.java -- FIXME: briefly describe file purpose
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.awt.font.autofit;
+
+public class Width
+{
+ int org;
+ int cur;
+ int fit;
+}
diff --git a/gnu/java/awt/font/opentype/CharGlyphMap.java b/gnu/java/awt/font/opentype/CharGlyphMap.java
index 6ada3b147..184075094 100644
--- a/gnu/java/awt/font/opentype/CharGlyphMap.java
+++ b/gnu/java/awt/font/opentype/CharGlyphMap.java
@@ -61,7 +61,7 @@ import java.nio.IntBuffer;
*
* @author Sascha Brawer (brawer@dandelis.ch)
*/
-abstract class CharGlyphMap
+public abstract class CharGlyphMap
{
private static final int PLATFORM_UNICODE = 0;
private static final int PLATFORM_MACINTOSH = 1;
diff --git a/gnu/java/awt/font/opentype/OpenTypeFont.java b/gnu/java/awt/font/opentype/OpenTypeFont.java
index 9ee28d76b..efc30811f 100644
--- a/gnu/java/awt/font/opentype/OpenTypeFont.java
+++ b/gnu/java/awt/font/opentype/OpenTypeFont.java
@@ -52,6 +52,7 @@ import java.util.Locale;
import gnu.java.awt.font.FontDelegate;
import gnu.java.awt.font.GNUGlyphVector;
import gnu.java.awt.font.opentype.truetype.TrueTypeScaler;
+import gnu.java.awt.font.opentype.truetype.Zone;
/**
@@ -117,7 +118,7 @@ public final class OpenTypeFont
* OpenType fonts with PostScript outlines, other values are
* acceptable (such as 1000).
*/
- private int unitsPerEm;
+ public int unitsPerEm;
/**
@@ -697,6 +698,20 @@ public final class OpenTypeFont
antialias, fractionalMetrics);
}
+ /**
+ * Fetches the raw glyph outline for the specified glyph index. This is used
+ * for the autofitter only ATM and is otherwise not usable for outside code.
+ *
+ * @param glyph the glyph index to fetch
+ * @param transform the transform to apply
+ *
+ * @return the raw outline of that glyph
+ */
+ public synchronized Zone getRawGlyphOutline(int glyph,
+ AffineTransform transform)
+ {
+ return scaler.getRawOutline(glyph, transform);
+ }
/**
* Returns a name for the specified glyph. This is useful for
diff --git a/gnu/java/awt/font/opentype/Scaler.java b/gnu/java/awt/font/opentype/Scaler.java
index 499c3ea52..83a31c576 100644
--- a/gnu/java/awt/font/opentype/Scaler.java
+++ b/gnu/java/awt/font/opentype/Scaler.java
@@ -37,6 +37,8 @@ exception statement from your version. */
package gnu.java.awt.font.opentype;
+import gnu.java.awt.font.opentype.truetype.Zone;
+
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
@@ -189,4 +191,14 @@ public abstract class Scaler
boolean antialiased,
boolean fractionalMetrics,
boolean horizontal);
+
+ /**
+ * Returns the raw outline data. This is used for the autofitter atm.
+ *
+ * @param glyph the glyph index
+ * @param transform the transform to apply
+ *
+ * @return the raw glyph outline
+ */
+ public abstract Zone getRawOutline(int glyph, AffineTransform transform);
}
diff --git a/gnu/java/awt/font/opentype/truetype/GlyphLoader.java b/gnu/java/awt/font/opentype/truetype/GlyphLoader.java
index b12d7782b..3733afe92 100644
--- a/gnu/java/awt/font/opentype/truetype/GlyphLoader.java
+++ b/gnu/java/awt/font/opentype/truetype/GlyphLoader.java
@@ -119,6 +119,11 @@ final class GlyphLoader
0, 0);
}
+ public void loadGlyph(int glyphIndex, AffineTransform transform,
+ Zone glyphZone)
+ {
+ loadGlyph(glyphIndex, unitsPerEm, transform, false, glyphZone);
+ }
private void loadSubGlyph(int glyphIndex,
double pointSize,
diff --git a/gnu/java/awt/font/opentype/truetype/TrueTypeScaler.java b/gnu/java/awt/font/opentype/truetype/TrueTypeScaler.java
index e4d7309cb..8dfdeff07 100644
--- a/gnu/java/awt/font/opentype/truetype/TrueTypeScaler.java
+++ b/gnu/java/awt/font/opentype/truetype/TrueTypeScaler.java
@@ -198,6 +198,12 @@ public final class TrueTypeScaler
return glyphZone.getPath();
}
+ public Zone getRawOutline(int glyphIndex, AffineTransform transform)
+ {
+ Zone zone = new Zone(glyphZone.getCapacity());
+ glyphLoader.loadGlyph(glyphIndex, transform, zone);
+ return zone;
+ }
/**
* Determines the advance width and height for a glyph.
diff --git a/gnu/java/awt/font/opentype/truetype/Zone.java b/gnu/java/awt/font/opentype/truetype/Zone.java
index c0a3947f6..ff5bb6316 100644
--- a/gnu/java/awt/font/opentype/truetype/Zone.java
+++ b/gnu/java/awt/font/opentype/truetype/Zone.java
@@ -45,7 +45,7 @@ import java.awt.geom.PathIterator;
/**
* A collection of points with some additional information.
*/
-final class Zone
+public final class Zone
{
private final int[] pos;
private final int[] origPos;
diff --git a/gnu/java/awt/java2d/QuadSegment.java b/gnu/java/awt/java2d/QuadSegment.java
index 5e15fe881..97a5372f6 100644
--- a/gnu/java/awt/java2d/QuadSegment.java
+++ b/gnu/java/awt/java2d/QuadSegment.java
@@ -145,7 +145,52 @@ public class QuadSegment extends Segment
Point2D cp;
QuadSegment s;
- if( plus )
+ if(!plus)
+ {
+ n1[0] = -n1[0];
+ n1[1] = -n1[1];
+ n2[0] = -n2[0];
+ n2[1] = -n2[1];
+ }
+
+ // Handle special cases where the control point is equal to an end point
+ // or end points are equal (ie, straight lines)
+ if (curve.getP1().equals(curve.getCtrlPt()))
+ {
+ cp = curve.getCtrlPt();
+ cp.setLocation(cp.getX() + n2[0], cp.getY() + n2[1]);
+ n1[0] = n2[0];
+ n1[1] = n2[1];
+ }
+ else if (curve.getP2().equals(curve.getCtrlPt()))
+ {
+ cp = curve.getCtrlPt();
+ cp.setLocation(cp.getX() + n1[0], cp.getY() + n1[1]);
+ n2[0] = n1[0];
+ n2[1] = n1[1];
+ }
+ else if (curve.getP1().equals(curve.getP2()))
+ {
+ cp = curve.getCtrlPt();
+
+ double deltaX = curve.getX1() - curve.getCtrlX();
+ double deltaY = curve.getY1() - curve.getCtrlY();
+ double length = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
+ double ratio = radius / length;
+ deltaX *= ratio;
+ deltaY *= ratio;
+
+ if (plus)
+ cp.setLocation(cp.getX() + deltaX, cp.getY() + deltaY);
+ else
+ cp.setLocation(cp.getX() - deltaX, cp.getY() - deltaY);
+ }
+ else if (n1[0] == n2[0] && n1[1] == n2[1])
+ {
+ cp = curve.getCtrlPt();
+ cp.setLocation(cp.getX() + n1[0], cp.getY() + n1[1]);
+ }
+ else
{
cp = lineIntersection(curve.getX1() + n1[0],
curve.getY1() + n1[1],
@@ -155,25 +200,11 @@ public class QuadSegment extends Segment
curve.getCtrlY() + n2[1],
curve.getX2() + n2[0],
curve.getY2() + n2[1], true);
- s = new QuadSegment(curve.getX1() + n1[0], curve.getY1() + n1[1],
- cp.getX(), cp.getY(),
- curve.getX2() + n2[0], curve.getY2() + n2[1]);
- }
- else
- {
- cp = lineIntersection(curve.getX1() - n1[0],
- curve.getY1() - n1[1],
- curve.getCtrlX() - n1[0],
- curve.getCtrlY() - n1[1],
- curve.getCtrlX() - n2[0],
- curve.getCtrlY() - n2[1],
- curve.getX2() - n2[0],
- curve.getY2() - n2[1], true);
-
- s = new QuadSegment(curve.getX1() - n1[0], curve.getY1() - n1[1],
- cp.getX(), cp.getY(),
- curve.getX2() - n2[0], curve.getY2() - n2[1]);
}
+
+ s = new QuadSegment(curve.getX1() + n1[0], curve.getY1() + n1[1],
+ cp.getX(), cp.getY(),
+ curve.getX2() + n2[0], curve.getY2() + n2[1]);
return s;
}
diff --git a/gnu/java/awt/java2d/TexturePaintContext.java b/gnu/java/awt/java2d/TexturePaintContext.java
index 840b61456..db0a2e658 100644
--- a/gnu/java/awt/java2d/TexturePaintContext.java
+++ b/gnu/java/awt/java2d/TexturePaintContext.java
@@ -177,6 +177,12 @@ public class TexturePaintContext
// The modulo operation gives us the replication effect.
dx = ((dx - minX) % width) + minX;
dy = ((dy - minY) % height) + minY;
+
+ // Handle possible negative values (replicating above the top-left)
+ if (dx < 0)
+ dx += width;
+ if (dy < 0)
+ dy += height;
// Copy the pixel.
pixel = source.getDataElements(dx, dy, pixel);
diff --git a/gnu/java/awt/peer/GLightweightPeer.java b/gnu/java/awt/peer/GLightweightPeer.java
index 3029502ff..f9a7bac8e 100644
--- a/gnu/java/awt/peer/GLightweightPeer.java
+++ b/gnu/java/awt/peer/GLightweightPeer.java
@@ -163,8 +163,10 @@ public class GLightweightPeer
public FontMetrics getFontMetrics(Font f)
{
- // Nothing to do here for lightweights.
- return null;
+ // We shouldn't end up here, but if we do we can still try do something
+ // reasonable.
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ return tk.getFontMetrics(f);
}
/* Returning null here tells the Component object that called us to
@@ -201,7 +203,31 @@ public class GLightweightPeer
public void handleEvent(AWTEvent e)
{
- // Nothing to do here for lightweights.
+ // This can only happen when an application posts a PaintEvent for
+ // a lightweight component directly. We still support painting for
+ // this case.
+ if (e instanceof PaintEvent)
+ {
+ PaintEvent pe = (PaintEvent) e;
+ Component target = (Component) e.getSource();
+ if (target != null && target.isShowing())
+ {
+ Graphics g = target.getGraphics();
+ if (g != null)
+ {
+ try
+ {
+ Rectangle clip = pe.getUpdateRect();
+ g.setClip(clip);
+ target.paint(g);
+ }
+ finally
+ {
+ g.dispose();
+ }
+ }
+ }
+ }
}
public void hide()
diff --git a/gnu/java/awt/peer/gtk/BufferedImageGraphics.java b/gnu/java/awt/peer/gtk/BufferedImageGraphics.java
index 341fa2a4e..7de9c057e 100644
--- a/gnu/java/awt/peer/gtk/BufferedImageGraphics.java
+++ b/gnu/java/awt/peer/gtk/BufferedImageGraphics.java
@@ -54,11 +54,11 @@ import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
-import java.awt.image.DirectColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
+import java.awt.image.SinglePixelPackedSampleModel;
import java.util.WeakHashMap;
/**
@@ -100,12 +100,6 @@ public class BufferedImageGraphics extends CairoGraphics2D
*/
private long cairo_t;
- /**
- * Colormodels we recognize for fast copying.
- */
- static ColorModel rgb32 = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF);
- static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF,
- 0xFF000000);
private boolean hasFastCM;
private boolean hasAlpha;
@@ -117,12 +111,14 @@ public class BufferedImageGraphics extends CairoGraphics2D
imageHeight = bi.getHeight();
locked = false;
- if(bi.getColorModel().equals(rgb32))
+ if (!(image.getSampleModel() instanceof SinglePixelPackedSampleModel))
+ hasFastCM = false;
+ else if(bi.getColorModel().equals(CairoSurface.cairoCM_opaque))
{
hasFastCM = true;
hasAlpha = false;
}
- else if(bi.getColorModel().equals(argb32))
+ else if(bi.getColorModel().equals(CairoSurface.cairoColorModel))
{
hasFastCM = true;
hasAlpha = true;
@@ -176,8 +172,11 @@ public class BufferedImageGraphics extends CairoGraphics2D
imageWidth = copyFrom.imageWidth;
imageHeight = copyFrom.imageHeight;
locked = false;
+
+ hasFastCM = copyFrom.hasFastCM;
+ hasAlpha = copyFrom.hasAlpha;
+
copy( copyFrom, cairo_t );
- setClip(0, 0, surface.width, surface.height);
}
/**
@@ -188,30 +187,79 @@ public class BufferedImageGraphics extends CairoGraphics2D
if (locked)
return;
+ double[] points = new double[]{x, y, width+x, height+y};
+ transform.transform(points, 0, points, 0, 2);
+ x = (int)points[0];
+ y = (int)points[1];
+ width = (int)Math.ceil(points[2] - points[0]);
+ height = (int)Math.ceil(points[3] - points[1]);
+
int[] pixels = surface.getPixels(imageWidth * imageHeight);
if( x > imageWidth || y > imageHeight )
return;
+
+ // Deal with negative width/height.
+ if (height < 0)
+ {
+ y += height;
+ height = -height;
+ }
+ if (width < 0)
+ {
+ x += width;
+ width = -width;
+ }
+
// Clip edges.
- if( x < 0 ){ width = width + x; x = 0; }
- if( y < 0 ){ height = height + y; y = 0; }
+ if( x < 0 )
+ x = 0;
+ if( y < 0 )
+ y = 0;
+
if( x + width > imageWidth )
width = imageWidth - x;
if( y + height > imageHeight )
height = imageHeight - y;
- // The setRGB method assumes (or should assume) that pixels are NOT
- // alpha-premultiplied, but Cairo stores data with premultiplication
- // (thus the pixels returned in getPixels are premultiplied).
- // This is ignored for consistency, however, since in
- // CairoGrahpics2D.drawImage we also use non-premultiplied data
if(!hasFastCM)
- image.setRGB(x, y, width, height, pixels,
- x + y * imageWidth, imageWidth);
+ {
+ image.setRGB(x, y, width, height, pixels,
+ x + y * imageWidth, imageWidth);
+ // The setRGB method assumes (or should assume) that pixels are NOT
+ // alpha-premultiplied, but Cairo stores data with premultiplication
+ // (thus the pixels returned in getPixels are premultiplied).
+ // This is ignored for consistency, however, since in
+ // CairoGrahpics2D.drawImage we also use non-premultiplied data
+
+ }
else
- System.arraycopy(pixels, y * imageWidth,
- ((DataBufferInt)image.getRaster().getDataBuffer()).
- getData(), y * imageWidth, height * imageWidth);
+ {
+ int[] db = ((DataBufferInt)image.getRaster().getDataBuffer()).
+ getData();
+
+ // This should not fail, as we check the image sample model when we
+ // set the hasFastCM flag
+ SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)image.getSampleModel() ;
+
+ int minX = image.getRaster().getSampleModelTranslateX();
+ int minY = image.getRaster().getSampleModelTranslateY();
+
+ if (sm.getScanlineStride() == imageWidth && minX == 0)
+ {
+ System.arraycopy(pixels, y * imageWidth,
+ db, y * imageWidth - minY,
+ height * imageWidth);
+ }
+ else
+ {
+ int scanline = sm.getScanlineStride();
+ for (int i = y; i < height; i++)
+ System.arraycopy(pixels, i * imageWidth + x, db,
+ (i - minY) * scanline + x - minX, width);
+
+ }
+ }
}
/**
@@ -244,15 +292,19 @@ public class BufferedImageGraphics extends CairoGraphics2D
*/
public void draw(Shape s)
{
+ // Find total bounds of shape
+ Rectangle r = findStrokedBounds(s);
+ if (shiftDrawCalls)
+ {
+ r.width++;
+ r.height++;
+ }
+
+ // Do the drawing
if (comp == null || comp instanceof AlphaComposite)
{
super.draw(s);
- Rectangle r = s.getBounds();
-
- if (shiftDrawCalls)
- updateBufferedImage(r.x, r.y, r.width+1, r.height+1);
- else
- updateBufferedImage(r.x, r.y, r.width, r.height);
+ updateBufferedImage(r.x, r.y, r.width, r.height);
}
else
{
@@ -263,7 +315,7 @@ public class BufferedImageGraphics extends CairoGraphics2D
g2d.setColor(this.getColor());
g2d.draw(s);
- drawComposite(s.getBounds2D(), null);
+ drawComposite(r.getBounds2D(), null);
}
}
@@ -356,10 +408,17 @@ public class BufferedImageGraphics extends CairoGraphics2D
public void drawGlyphVector(GlyphVector gv, float x, float y)
{
+ // Find absolute bounds, in user-space, of this glyph vector
+ Rectangle2D bounds = gv.getLogicalBounds();
+ bounds = new Rectangle2D.Double(x + bounds.getX(), y + bounds.getY(),
+ bounds.getWidth(), bounds.getHeight());
+
+ // Perform draw operation
if (comp == null || comp instanceof AlphaComposite)
{
super.drawGlyphVector(gv, x, y);
- updateBufferedImage(0, 0, imageWidth, imageHeight);
+ updateBufferedImage((int)bounds.getX(), (int)bounds.getY(),
+ (int)bounds.getWidth(), (int)bounds.getHeight());
}
else
{
@@ -370,9 +429,6 @@ public class BufferedImageGraphics extends CairoGraphics2D
g2d.setStroke(this.getStroke());
g2d.drawGlyphVector(gv, x, y);
- Rectangle2D bounds = gv.getLogicalBounds();
- bounds = new Rectangle2D.Double(x + bounds.getX(), y + bounds.getY(),
- bounds.getWidth(), bounds.getHeight());
drawComposite(bounds, null);
}
}
diff --git a/gnu/java/awt/peer/gtk/CairoGraphics2D.java b/gnu/java/awt/peer/gtk/CairoGraphics2D.java
index ce3041d73..16de95185 100644
--- a/gnu/java/awt/peer/gtk/CairoGraphics2D.java
+++ b/gnu/java/awt/peer/gtk/CairoGraphics2D.java
@@ -71,6 +71,7 @@ import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
@@ -134,6 +135,7 @@ public abstract class CairoGraphics2D extends Graphics2D
* The current paint
*/
Paint paint;
+ boolean customPaint;
/**
* The current stroke
@@ -255,6 +257,8 @@ public abstract class CairoGraphics2D extends Graphics2D
bg = new Color(g.bg.getRGB());
}
+ firstClip = g.firstClip;
+ originalClip = g.originalClip;
clip = g.getClip();
if (g.transform == null)
@@ -311,6 +315,11 @@ public abstract class CairoGraphics2D extends Graphics2D
int width, int height, int dx, int dy);
+ /**
+ * Find the bounds of this graphics context, in device space.
+ *
+ * @return the bounds in device-space
+ */
protected abstract Rectangle2D getRealBounds();
////// Native Methods ////////////////////////////////////////////////////
@@ -336,7 +345,8 @@ public abstract class CairoGraphics2D extends Graphics2D
int g2, int b2, int a2, boolean cyclic);
private native void setPaintPixels(long pointer, int[] pixels, int w,
- int h, int stride, boolean repeat);
+ int h, int stride, boolean repeat,
+ int x, int y);
/**
* Set the current transform matrix
@@ -691,6 +701,7 @@ public abstract class CairoGraphics2D extends Graphics2D
if (paint instanceof Color)
{
setColor((Color) paint);
+ customPaint = false;
}
else if (paint instanceof TexturePaint)
{
@@ -708,7 +719,8 @@ public abstract class CairoGraphics2D extends Graphics2D
AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
BufferedImage texture = op.filter(img, null);
int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
- setPaintPixels(nativePointer, pixels, width, height, width, true);
+ setPaintPixels(nativePointer, pixels, width, height, width, true, 0, 0);
+ customPaint = false;
}
else if (paint instanceof GradientPaint)
{
@@ -721,36 +733,90 @@ public abstract class CairoGraphics2D extends Graphics2D
c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(),
c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(),
gp.isCyclic());
+ customPaint = false;
}
else
{
- // Get bounds in device space
- int minX = 0;
- int minY = 0;
- int width = (int)getRealBounds().getWidth();
- int height = (int)getRealBounds().getHeight();
-
- Point2D origin = transform.transform(new Point2D.Double(minX, minY),
- null);
- Point2D extreme = transform.transform(new Point2D.Double(width + minX,
- height + minY),
- null);
- minX = (int)origin.getX();
- minY = (int)origin.getY();
- width = (int)extreme.getX() - minX;
- height = (int)extreme.getY() - minY;
-
- // Get raster of the paint background
- PaintContext pc = paint.createContext(ColorModel.getRGBdefault(),
- new Rectangle(minX, minY,
- width, height),
- getRealBounds(),
- transform, hints);
+ customPaint = true;
+ }
+ }
+
+ /**
+ * Sets a custom paint
+ *
+ * @param bounds the bounding box, in user space
+ */
+ protected void setCustomPaint(Rectangle bounds)
+ {
+ if (paint instanceof Color || paint instanceof TexturePaint
+ || paint instanceof GradientPaint)
+ return;
+
+ int userX = bounds.x;
+ int userY = bounds.y;
+ int userWidth = bounds.width;
+ int userHeight = bounds.height;
+
+ // Find bounds in device space
+ Point2D origin = transform.transform(new Point2D.Double(userX, userY),
+ null);
+ Point2D extreme = transform.transform(new Point2D.Double(userWidth + userX,
+ userHeight + userY),
+ null);
+ int deviceX = (int)origin.getX();
+ int deviceY = (int)origin.getY();
+ int deviceWidth = (int)Math.ceil(extreme.getX() - origin.getX());
+ int deviceHeight = (int)Math.ceil(extreme.getY() - origin.getY());
+
+ // Get raster of the paint background
+ PaintContext pc = paint.createContext(CairoSurface.cairoColorModel,
+ new Rectangle(deviceX, deviceY,
+ deviceWidth,
+ deviceHeight),
+ bounds,
+ transform, hints);
+
+ Raster raster = pc.getRaster(deviceX, deviceY, deviceWidth,
+ deviceHeight);
+
+ // Clear the transform matrix in Cairo, since the raster returned by the
+ // PaintContext is already in device-space
+ AffineTransform oldTx = new AffineTransform(transform);
+ setTransformImpl(new AffineTransform());
+
+ // Set pixels in cairo, aligning the top-left of the background image
+ // to the top-left corner in device space
+ if (pc.getColorModel().equals(CairoSurface.cairoColorModel)
+ && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
+ {
+ // Use a fast copy if the paint context can uses a Cairo-compatible
+ // color model
+ setPaintPixels(nativePointer,
+ (int[])raster.getDataElements(0, 0, deviceWidth,
+ deviceHeight, null),
+ deviceWidth, deviceHeight, deviceWidth, false,
+ deviceX, deviceY);
+ }
+
+ else if (pc.getColorModel().equals(CairoSurface.cairoCM_opaque)
+ && raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
+ {
+ // We can also optimize if the context uses a similar color model
+ // but without an alpha channel; we just add the alpha
+ int[] pixels = (int[])raster.getDataElements(0, 0, deviceWidth,
+ deviceHeight, null);
- Raster raster = pc.getRaster(minX, minY, width, height);
+ for (int i = 0; i < pixels.length; i++)
+ pixels[i] = 0xff000000 | (pixels[i] & 0x00ffffff);
- // Work around colorspace issues, and force use of the
- // BufferedImage.getRGB method... this can be improved upon.
+ setPaintPixels(nativePointer, pixels, deviceWidth, deviceHeight,
+ deviceWidth, false, deviceX, deviceY);
+ }
+
+ else
+ {
+ // Fall back on wrapping the raster in a BufferedImage, and
+ // use BufferedImage.getRGB() to do color-model conversion
WritableRaster wr = Raster.createWritableRaster(raster.getSampleModel(),
new Point(raster.getMinX(),
raster.getMinY()));
@@ -760,15 +826,15 @@ public abstract class CairoGraphics2D extends Graphics2D
pc.getColorModel().isAlphaPremultiplied(),
null);
- // Set pixels in cairo
setPaintPixels(nativePointer,
- img2.getRGB(0, 0, width, height, null, 0, width),
- width, height, width, false);
- // setPaintPixels(nativePointer,
- // raster.getPixels(0, 0, width, height, (int[])null),
- // width, height, width, false);
- // doesn't work... but would be much more efficient!
+ img2.getRGB(0, 0, deviceWidth, deviceHeight, null, 0,
+ deviceWidth),
+ deviceWidth, deviceHeight, deviceWidth, false,
+ deviceX, deviceY);
}
+
+ // Restore transform
+ setTransformImpl(oldTx);
}
public Stroke getStroke()
@@ -799,6 +865,33 @@ public abstract class CairoGraphics2D extends Graphics2D
}
}
+ /**
+ * Utility method to find the bounds of a shape, including the stroke width.
+ *
+ * @param s the shape
+ * @return the bounds of the shape, including stroke width
+ */
+ protected Rectangle findStrokedBounds(Shape s)
+ {
+ Rectangle r = s.getBounds();
+
+ if (stroke instanceof BasicStroke)
+ {
+ int strokeWidth = (int)Math.ceil(((BasicStroke)stroke).getLineWidth());
+ r.x -= strokeWidth / 2;
+ r.y -= strokeWidth / 2;
+ r.height += strokeWidth;
+ r.width += strokeWidth;
+ }
+ else
+ {
+ Shape s2 = stroke.createStrokedShape(s);
+ r = s2.getBounds();
+ }
+
+ return r;
+ }
+
public void setPaintMode()
{
setComposite(AlphaComposite.SrcOver);
@@ -1023,6 +1116,12 @@ public abstract class CairoGraphics2D extends Graphics2D
return;
}
+ if (customPaint)
+ {
+ Rectangle r = findStrokedBounds(s);
+ setCustomPaint(r);
+ }
+
createPath(s, true);
cairoStroke(nativePointer);
}
@@ -1031,6 +1130,9 @@ public abstract class CairoGraphics2D extends Graphics2D
{
createPath(s, false);
+ if (customPaint)
+ setCustomPaint(s.getBounds());
+
double alpha = 1.0;
if (comp instanceof AlphaComposite)
alpha = ((AlphaComposite) comp).getAlpha();
@@ -1045,9 +1147,25 @@ public abstract class CairoGraphics2D extends Graphics2D
if (s instanceof Rectangle2D)
{
Rectangle2D r = (Rectangle2D) s;
- cairoRectangle(nativePointer, shifted(r.getX(),shiftDrawCalls && isDraw),
- shifted(r.getY(), shiftDrawCalls && isDraw), r.getWidth(),
- r.getHeight());
+
+ // Pixels need to be shifted in draw operations to ensure that they
+ // light up entire pixels, but we also need to make sure the rectangle
+ // does not get distorted by this shifting operation
+ double x = shiftX(r.getX(),shiftDrawCalls && isDraw);
+ double y = shiftY(r.getY(), shiftDrawCalls && isDraw);
+ double w = shiftX(r.getWidth() + r.getX(), shiftDrawCalls && isDraw) - x;
+ double h = shiftY(r.getHeight() + r.getY(), shiftDrawCalls && isDraw) - y;
+ cairoRectangle(nativePointer, x, y, w, h);
+ }
+
+ // Lines are easy too
+ else if (s instanceof Line2D)
+ {
+ Line2D l = (Line2D) s;
+ cairoMoveTo(nativePointer, shiftX(l.getX1(), shiftDrawCalls && isDraw),
+ shiftY(l.getY1(), shiftDrawCalls && isDraw));
+ cairoLineTo(nativePointer, shiftX(l.getX2(), shiftDrawCalls && isDraw),
+ shiftY(l.getY2(), shiftDrawCalls && isDraw));
}
// We can optimize ellipses too; however we don't bother optimizing arcs:
@@ -1076,8 +1194,8 @@ public abstract class CairoGraphics2D extends Graphics2D
}
cairoArc(nativePointer,
- shifted(e.getCenterX() / xscale, shiftDrawCalls && isDraw),
- shifted(e.getCenterY() / yscale, shiftDrawCalls && isDraw),
+ shiftX(e.getCenterX() / xscale, shiftDrawCalls && isDraw),
+ shiftY(e.getCenterY() / yscale, shiftDrawCalls && isDraw),
radius, 0, Math.PI * 2);
if (xscale != 1 || yscale != 1)
@@ -1133,15 +1251,14 @@ public abstract class CairoGraphics2D extends Graphics2D
// to draw a single pixel. This is emulated by drawing
// a one pixel sized rectangle.
if (x1 == x2 && y1 == y2)
- cairoFillRect(nativePointer, x1, y1, 1, 1);
+ fill(new Rectangle(x1, y1, 1, 1));
else
- cairoDrawLine(nativePointer, x1 + 0.5, y1 + 0.5, x2 + 0.5, y2 + 0.5);
+ draw(new Line2D.Double(x1, y1, x2, y2));
}
public void drawRect(int x, int y, int width, int height)
{
- cairoDrawRect(nativePointer, shifted(x, shiftDrawCalls),
- shifted(y, shiftDrawCalls), width, height);
+ draw(new Rectangle(x, y, width, height));
}
public void fillArc(int x, int y, int width, int height, int startAngle,
@@ -1154,10 +1271,7 @@ public abstract class CairoGraphics2D extends Graphics2D
public void fillRect(int x, int y, int width, int height)
{
- fill(new Rectangle(x, y, width, height));
- // TODO: If we want to use the more efficient
- //cairoFillRect(nativePointer, x, y, width, height);
- // we need to override this method in subclasses
+ fill (new Rectangle(x, y, width, height));
}
public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
@@ -1523,8 +1637,14 @@ public abstract class CairoGraphics2D extends Graphics2D
{
if (str == null || str.length() == 0)
return;
- (new TextLayout( str, getFont(), getFontRenderContext() )).
- draw(this, x, y);
+ GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
+ TextLayout tl = (TextLayout) fontPeer.textLayoutCache.get(str);
+ if (tl == null)
+ {
+ tl = new TextLayout( str, getFont(), getFontRenderContext() );
+ fontPeer.textLayoutCache.put(str, tl);
+ }
+ tl.draw(this, x, y);
}
public void drawString(String str, int x, int y)
@@ -1544,6 +1664,9 @@ public abstract class CairoGraphics2D extends Graphics2D
if( gv.getNumGlyphs() <= 0 )
return;
+ if (customPaint)
+ setCustomPaint(gv.getOutline().getBounds());
+
if (comp instanceof AlphaComposite)
alpha = ((AlphaComposite) comp).getAlpha();
if (gv instanceof FreetypeGlyphVector && alpha == 1.0)
@@ -1553,9 +1676,10 @@ public abstract class CairoGraphics2D extends Graphics2D
float[] positions = gv.getGlyphPositions (0, n, null);
setFont (gv.getFont ());
- synchronized( this.font )
+ GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
+ synchronized (fontPeer)
{
- cairoDrawGlyphVector(nativePointer, (GdkFontPeer)getFont().getPeer(),
+ cairoDrawGlyphVector(nativePointer, fontPeer,
x, y, n, codes, positions);
}
}
@@ -1593,9 +1717,7 @@ public abstract class CairoGraphics2D extends Graphics2D
public FontMetrics getFontMetrics(Font f)
{
- // the reason we go via the toolkit here is to try to get
- // a cached object. the toolkit keeps such a cache.
- return Toolkit.getDefaultToolkit().getFontMetrics(f);
+ return ((GdkFontPeer) f.getPeer()).getFontMetrics(f);
}
public void setFont(Font f)
@@ -1612,7 +1734,11 @@ public abstract class CairoGraphics2D extends Graphics2D
((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
.getFont(f.getName(), f.getAttributes());
- cairoSetFont(nativePointer, (GdkFontPeer)getFont().getPeer());
+ GdkFontPeer fontpeer = (GdkFontPeer) getFont().getPeer();
+ synchronized (fontpeer)
+ {
+ cairoSetFont(nativePointer, fontpeer);
+ }
}
public Font getFont()
@@ -1732,12 +1858,33 @@ public abstract class CairoGraphics2D extends Graphics2D
}
/**
- * Shifts coordinates by 0.5.
+ * Shifts an x-coordinate by 0.5 in device space.
+ */
+ private double shiftX(double coord, boolean doShift)
+ {
+ if (doShift)
+ {
+ double shift = 0.5;
+ if (!transform.isIdentity())
+ shift /= transform.getScaleX();
+ return Math.round(coord) + shift;
+ }
+ else
+ return coord;
+ }
+
+ /**
+ * Shifts a y-coordinate by 0.5 in device space.
*/
- private double shifted(double coord, boolean doShift)
+ private double shiftY(double coord, boolean doShift)
{
if (doShift)
- return Math.floor(coord) + 0.5;
+ {
+ double shift = 0.5;
+ if (!transform.isIdentity())
+ shift /= transform.getScaleY();
+ return Math.round(coord) + shift;
+ }
else
return coord;
}
@@ -1758,35 +1905,35 @@ public abstract class CairoGraphics2D extends Graphics2D
switch (seg)
{
case PathIterator.SEG_MOVETO:
- x = shifted(coords[0], doShift);
- y = shifted(coords[1], doShift);
+ x = shiftX(coords[0], doShift);
+ y = shiftY(coords[1], doShift);
cairoMoveTo(nativePointer, x, y);
break;
case PathIterator.SEG_LINETO:
- x = shifted(coords[0], doShift);
- y = shifted(coords[1], doShift);
+ x = shiftX(coords[0], doShift);
+ y = shiftY(coords[1], doShift);
cairoLineTo(nativePointer, x, y);
break;
case PathIterator.SEG_QUADTO:
// splitting a quadratic bezier into a cubic:
// see: http://pfaedit.sourceforge.net/bezier.html
- double x1 = x + (2.0 / 3.0) * (shifted(coords[0], doShift) - x);
- double y1 = y + (2.0 / 3.0) * (shifted(coords[1], doShift) - y);
+ double x1 = x + (2.0 / 3.0) * (shiftX(coords[0], doShift) - x);
+ double y1 = y + (2.0 / 3.0) * (shiftY(coords[1], doShift) - y);
- double x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x);
- double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y);
+ double x2 = x1 + (1.0 / 3.0) * (shiftX(coords[2], doShift) - x);
+ double y2 = y1 + (1.0 / 3.0) * (shiftY(coords[3], doShift) - y);
- x = shifted(coords[2], doShift);
- y = shifted(coords[3], doShift);
+ x = shiftX(coords[2], doShift);
+ y = shiftY(coords[3], doShift);
cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y);
break;
case PathIterator.SEG_CUBICTO:
- x = shifted(coords[4], doShift);
- y = shifted(coords[5], doShift);
- cairoCurveTo(nativePointer, shifted(coords[0], doShift),
- shifted(coords[1], doShift),
- shifted(coords[2], doShift),
- shifted(coords[3], doShift), x, y);
+ x = shiftX(coords[4], doShift);
+ y = shiftY(coords[5], doShift);
+ cairoCurveTo(nativePointer, shiftX(coords[0], doShift),
+ shiftY(coords[1], doShift),
+ shiftX(coords[2], doShift),
+ shiftY(coords[3], doShift), x, y);
break;
case PathIterator.SEG_CLOSE:
cairoClosePath(nativePointer);
diff --git a/gnu/java/awt/peer/gtk/CairoSurface.java b/gnu/java/awt/peer/gtk/CairoSurface.java
index d3b3d4504..5b63e62e7 100644
--- a/gnu/java/awt/peer/gtk/CairoSurface.java
+++ b/gnu/java/awt/peer/gtk/CairoSurface.java
@@ -88,6 +88,11 @@ public class CairoSurface extends WritableRaster
0xFF000000,
true,
Buffers.smallestAppropriateTransferType(32));
+
+ // This CM corresponds to the CAIRO_FORMAT_RGB24 type in Cairo
+ static ColorModel cairoCM_opaque = new DirectColorModel(24, 0x00FF0000,
+ 0x0000FF00,
+ 0x000000FF);
/**
* Allocates and clears the buffer and creates the cairo surface.
* @param width, height - the image size
diff --git a/gnu/java/awt/peer/gtk/ComponentGraphics.java b/gnu/java/awt/peer/gtk/ComponentGraphics.java
index c8ec2c95e..612efd628 100644
--- a/gnu/java/awt/peer/gtk/ComponentGraphics.java
+++ b/gnu/java/awt/peer/gtk/ComponentGraphics.java
@@ -480,57 +480,6 @@ public class ComponentGraphics extends CairoGraphics2D
return super.drawImage(bimg, x, y, width, height, observer);
}
- public void drawLine(int x1, int y1, int x2, int y2)
- {
- lock();
- try
- {
- if (comp == null || comp instanceof AlphaComposite)
- super.drawLine(x1, y1, x2, y2);
-
- else
- draw(new Line2D.Double(x1, y1, x2, y2));
- }
- finally
- {
- unlock();
- }
- }
-
- public void drawRect(int x, int y, int width, int height)
- {
- lock();
- try
- {
- if (comp == null || comp instanceof AlphaComposite)
- super.drawRect(x, y, width, height);
-
- else
- draw(new Rectangle2D.Double(x, y, width, height));
- }
- finally
- {
- unlock();
- }
- }
-
- public void fillRect(int x, int y, int width, int height)
- {
- lock();
- try
- {
- if (comp == null || comp instanceof AlphaComposite)
- super.fillRect(x, y, width, height);
-
- else
- fill(new Rectangle2D.Double(x, y, width, height));
- }
- finally
- {
- unlock();
- }
- }
-
public void setClip(Shape s)
{
lock();
diff --git a/gnu/java/awt/peer/gtk/FreetypeGlyphVector.java b/gnu/java/awt/peer/gtk/FreetypeGlyphVector.java
index 98f1bd69b..4ad87a84b 100644
--- a/gnu/java/awt/peer/gtk/FreetypeGlyphVector.java
+++ b/gnu/java/awt/peer/gtk/FreetypeGlyphVector.java
@@ -294,7 +294,7 @@ public class FreetypeGlyphVector extends GlyphVector
{
int[] rval;
- if( codeReturn == null )
+ if( codeReturn == null || codeReturn.length < numEntries)
rval = new int[ numEntries ];
else
rval = codeReturn;
@@ -395,7 +395,7 @@ public class FreetypeGlyphVector extends GlyphVector
public float[] getGlyphPositions(int beginGlyphIndex, int numEntries,
float[] positionReturn)
{
- if (positionReturn == null)
+ if (positionReturn == null || positionReturn.length < (numEntries * 2))
positionReturn = new float[numEntries*2];
System.arraycopy(glyphPositions, beginGlyphIndex*2, positionReturn, 0,
diff --git a/gnu/java/awt/peer/gtk/GdkFontMetrics.java b/gnu/java/awt/peer/gtk/GdkFontMetrics.java
deleted file mode 100644
index 30b1ff15e..000000000
--- a/gnu/java/awt/peer/gtk/GdkFontMetrics.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/* GdkFontMetrics.java
- Copyright (C) 1999, 2002, 2004, 2005 Free Software Foundation, Inc.
-
-This file is part of GNU Classpath.
-
-GNU Classpath is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
-any later version.
-
-GNU Classpath is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GNU Classpath; see the file COPYING. If not, write to the
-Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-02110-1301 USA.
-
-Linking this library statically or dynamically with other modules is
-making a combined work based on this library. Thus, the terms and
-conditions of the GNU General Public License cover the whole
-combination.
-
-As a special exception, the copyright holders of this library give you
-permission to link this library with independent modules to produce an
-executable, regardless of the license terms of these independent
-modules, and to copy and distribute the resulting executable under
-terms of your choice, provided that you also meet, for each linked
-independent module, the terms and conditions of the license of that
-module. An independent module is a module which is not derived from
-or based on this library. If you modify this library, you may extend
-this exception to your version of the library, but you are not
-obligated to do so. If you do not wish to do so, delete this
-exception statement from your version. */
-
-
-package gnu.java.awt.peer.gtk;
-
-import gnu.java.awt.ClasspathToolkit;
-
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Toolkit;
-
-public class GdkFontMetrics extends FontMetrics
-{
-
- private int[] font_metrics;
- GdkFontPeer peer;
-
- static final int FONT_METRICS_ASCENT = 0;
- static final int FONT_METRICS_MAX_ASCENT = 1;
- static final int FONT_METRICS_DESCENT = 2;
- static final int FONT_METRICS_MAX_DESCENT = 3;
- static final int FONT_METRICS_MAX_ADVANCE = 4;
-
- static final int TEXT_METRICS_X_BEARING = 0;
- static final int TEXT_METRICS_Y_BEARING = 1;
- static final int TEXT_METRICS_WIDTH = 2;
- static final int TEXT_METRICS_HEIGHT = 3;
- static final int TEXT_METRICS_X_ADVANCE = 4;
- static final int TEXT_METRICS_Y_ADVANCE = 5;
-
- /**
- * Makes sure to return a Font based on the given Font that has as
- * peer a GdkFontPeer. Used in the initializer.
- */
- private static Font initFont(Font font)
- {
- if (font == null)
- return new Font("Dialog", Font.PLAIN, 12);
- else if (font.getPeer() instanceof GdkFontPeer)
- return font;
- else
- {
- ClasspathToolkit toolkit;
- toolkit = (ClasspathToolkit) Toolkit.getDefaultToolkit();
- return toolkit.getFont(font.getName(), font.getAttributes());
- }
- }
-
- public GdkFontMetrics (Font font)
- {
- super(initFont(font));
- peer = (GdkFontPeer) this.font.getPeer();
-
- font_metrics = new int[5];
- double [] hires = new double[5];
- peer.getFontMetrics (hires);
- for (int i = 0; i < 5; ++i)
- font_metrics[i] = (int) hires[i];
- }
-
- public int stringWidth (String str)
- {
-
-// TextLayout layout = new TextLayout(str, getFont(), null);
-// return (int) layout.getAdvance();
- // We need to filter control chars, because they are never displayed
- // in Java and thus also have no metrics.
- int len = str.length();
- StringBuilder b = new StringBuilder(len);
- for (int i = 0; i < len; i++)
- {
- char ch = str.charAt(i);
- if (! Character.isISOControl(ch))
- b.append(ch);
- }
- double [] hires = new double[6];
- String string = b.toString();
- peer.getTextMetrics(string, hires);
- return (int) hires [TEXT_METRICS_WIDTH];
- }
-
- public int charWidth (char ch)
- {
- return stringWidth (new String (new char[] { ch }));
- }
-
- public int charsWidth (char data[], int off, int len)
- {
- return stringWidth (new String (data, off, len));
- }
-
- public int getLeading ()
- {
- // Sun always returns 0.
- return 0;
- }
-
- public int getAscent ()
- {
- return font_metrics[FONT_METRICS_ASCENT];
- }
-
- public int getMaxAscent ()
- {
- return font_metrics[FONT_METRICS_MAX_ASCENT];
- }
-
- public int getDescent ()
- {
- return font_metrics[FONT_METRICS_DESCENT];
- }
-
- public int getMaxDescent ()
- {
- return font_metrics[FONT_METRICS_MAX_DESCENT];
- }
-
- public int getMaxAdvance ()
- {
- return font_metrics[FONT_METRICS_MAX_ADVANCE];
- }
-}
diff --git a/gnu/java/awt/peer/gtk/GdkFontPeer.java b/gnu/java/awt/peer/gtk/GdkFontPeer.java
index 40b234984..5f5126ac5 100644
--- a/gnu/java/awt/peer/gtk/GdkFontPeer.java
+++ b/gnu/java/awt/peer/gtk/GdkFontPeer.java
@@ -38,6 +38,7 @@ exception statement from your version. */
package gnu.java.awt.peer.gtk;
+import gnu.java.awt.ClasspathToolkit;
import gnu.java.awt.peer.ClasspathFontPeer;
import gnu.java.awt.font.opentype.NameDecoder;
@@ -48,39 +49,125 @@ import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.font.GlyphMetrics;
import java.awt.font.LineMetrics;
+import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.text.CharacterIterator;
import java.util.Locale;
import java.util.Map;
-import java.util.ResourceBundle;
import java.nio.ByteBuffer;
import java.util.HashMap;
public class GdkFontPeer extends ClasspathFontPeer
{
+ static final FontRenderContext DEFAULT_CTX =
+ new FontRenderContext(null, false, false);
+
+ /**
+ * Caches TextLayout instances for use in charsWidth() and drawString().
+ * The size of the cache has been chosen so that relativly large GUIs with
+ * text documents are still efficient.
+ */
+ HashMap textLayoutCache = new GtkToolkit.LRUCache(500);
+
+ private class GdkFontMetrics extends FontMetrics
+ {
+
+ public GdkFontMetrics (Font font)
+ {
+ super(initFont(font));
+ }
+
+ public int stringWidth (String str)
+ {
+ TextLayout tl = (TextLayout) textLayoutCache.get(str);
+ if (tl == null)
+ {
+ tl = new TextLayout(str, font, DEFAULT_CTX);
+ textLayoutCache.put(str, tl);
+ }
+ return (int) tl.getAdvance();
+ }
+
+ public int charWidth (char ch)
+ {
+ return stringWidth (new String (new char[] { ch }));
+ }
+
+ public int charsWidth (char data[], int off, int len)
+ {
+ return stringWidth (new String (data, off, len));
+ }
+
+ public int getHeight()
+ {
+ return (int) height;
+ }
+
+ public int getLeading ()
+ {
+ return (int) (height - (ascent + descent));
+ }
+
+ public int getAscent ()
+ {
+ return (int) ascent;
+ }
+
+ public int getMaxAscent ()
+ {
+ return (int) ascent;
+ }
+
+ public int getDescent ()
+ {
+ return (int) descent;
+ }
+
+ public int getMaxDescent ()
+ {
+ return (int) maxDescent;
+ }
+
+ public int getMaxAdvance ()
+ {
+ return (int) maxAdvance;
+ }
+ }
+
static native void initStaticState();
private final int native_state = GtkGenericPeer.getUniqueInteger ();
- private static ResourceBundle bundle;
/**
* Cache GlyphMetrics objects.
*/
private HashMap metricsCache;
-
+
+ private static final int FONT_METRICS_ASCENT = 0;
+ private static final int FONT_METRICS_MAX_ASCENT = 1;
+ private static final int FONT_METRICS_DESCENT = 2;
+ private static final int FONT_METRICS_MAX_DESCENT = 3;
+ private static final int FONT_METRICS_MAX_ADVANCE = 4;
+ private static final int FONT_METRICS_HEIGHT = 5;
+ private static final int FONT_METRICS_UNDERLINE_OFFSET = 6;
+ private static final int FONT_METRICS_UNDERLINE_THICKNESS = 7;
+
+ float ascent;
+ float descent;
+ float maxAscent;
+ float maxDescent;
+ float maxAdvance;
+ float height;
+ float underlineOffset;
+ float underlineThickness;
+
+ GdkFontMetrics metrics;
+
static
{
System.loadLibrary("gtkpeer");
initStaticState ();
- try
- {
- bundle = ResourceBundle.getBundle ("gnu.java.awt.peer.gtk.font");
- }
- catch (Throwable ignored)
- {
- bundle = null;
- }
}
private ByteBuffer nameTable = null;
@@ -89,8 +176,8 @@ public class GdkFontPeer extends ClasspathFontPeer
private native void dispose ();
private native void setFont (String family, int style, int size);
- native void getFontMetrics(double [] metrics);
- native void getTextMetrics(String str, double [] metrics);
+ native synchronized void getFontMetrics(double [] metrics);
+ native synchronized void getTextMetrics(String str, double [] metrics);
native void releasePeerGraphicsResource();
@@ -149,6 +236,7 @@ public class GdkFontPeer extends ClasspathFontPeer
initState ();
setFont (this.familyName, this.style, (int)this.size);
metricsCache = new HashMap();
+ setupMetrics();
}
public GdkFontPeer (String name, Map attributes)
@@ -157,6 +245,40 @@ public class GdkFontPeer extends ClasspathFontPeer
initState ();
setFont (this.familyName, this.style, (int)this.size);
metricsCache = new HashMap();
+ setupMetrics();
+ }
+
+
+ /**
+ * Makes sure to return a Font based on the given Font that has as
+ * peer a GdkFontPeer. Used in the initializer.
+ */
+ static Font initFont(Font font)
+ {
+ if (font == null)
+ return new Font("Dialog", Font.PLAIN, 12);
+ else if (font.getPeer() instanceof GdkFontPeer)
+ return font;
+ else
+ {
+ ClasspathToolkit toolkit;
+ toolkit = (ClasspathToolkit) Toolkit.getDefaultToolkit();
+ return toolkit.getFont(font.getName(), font.getAttributes());
+ }
+ }
+
+ private void setupMetrics()
+ {
+ double [] hires = new double[8];
+ getFontMetrics(hires);
+ ascent = (float) hires[FONT_METRICS_ASCENT];
+ maxAscent = (float) hires[FONT_METRICS_MAX_ASCENT];
+ descent = (float) hires[FONT_METRICS_DESCENT];
+ maxDescent = (float) hires[FONT_METRICS_MAX_DESCENT];
+ maxAdvance = (float) hires[FONT_METRICS_MAX_ADVANCE];
+ height = (float) hires[FONT_METRICS_HEIGHT];
+ underlineOffset = (float) hires[FONT_METRICS_UNDERLINE_OFFSET];
+ underlineThickness = (float) hires[FONT_METRICS_UNDERLINE_THICKNESS];
}
/**
@@ -261,26 +383,17 @@ public class GdkFontPeer extends ClasspathFontPeer
return Font.ROMAN_BASELINE;
}
- private static class GdkFontLineMetrics extends LineMetrics
+ private class GdkFontLineMetrics extends LineMetrics
{
- private FontMetrics fm;
- private int nchars;
- private float strikethroughOffset, strikethroughThickness,
- underlineOffset, underlineThickness;
-
- public GdkFontLineMetrics (GdkFontPeer fp, FontMetrics m, int n)
+ private int nchars;
+ public GdkFontLineMetrics (GdkFontPeer fp, int n)
{
- fm = m;
nchars = n;
- strikethroughOffset = 0f;
- underlineOffset = 0f;
- strikethroughThickness = ((float)fp.getSize(null)) / 12f;
- underlineThickness = strikethroughThickness;
}
public float getAscent()
{
- return (float) fm.getAscent ();
+ return ascent;
}
public int getBaselineIndex()
@@ -296,27 +409,52 @@ public class GdkFontPeer extends ClasspathFontPeer
public float getDescent()
{
- return (float) fm.getDescent ();
+ return descent;
}
public float getHeight()
{
- return (float) fm.getHeight ();
+ return height;
}
- public float getLeading() { return 0.f; }
- public int getNumChars() { return nchars; }
- public float getStrikethroughOffset() { return 0.f; }
- public float getStrikethroughThickness() { return 0.f; }
- public float getUnderlineOffset() { return 0.f; }
- public float getUnderlineThickness() { return 0.f; }
+ public float getLeading()
+ {
+ return height - (ascent + descent);
+ }
+
+ public int getNumChars()
+ {
+ return nchars;
+ }
+
+ public float getStrikethroughOffset()
+ {
+ // FreeType doesn't seem to provide a value here.
+ return ascent / 2;
+ }
+
+ public float getStrikethroughThickness()
+ {
+ // FreeType doesn't seem to provide a value here.
+ return 1.f;
+ }
+
+ public float getUnderlineOffset()
+ {
+ return underlineOffset;
+ }
+
+ public float getUnderlineThickness()
+ {
+ return underlineThickness;
+ }
}
public LineMetrics getLineMetrics (Font font, CharacterIterator ci,
int begin, int limit, FontRenderContext rc)
{
- return new GdkFontLineMetrics (this, getFontMetrics (font), limit - begin);
+ return new GdkFontLineMetrics (this, limit - begin);
}
public Rectangle2D getMaxCharBounds (Font font, FontRenderContext rc)
@@ -361,14 +499,14 @@ public class GdkFontPeer extends ClasspathFontPeer
public LineMetrics getLineMetrics (Font font, String str,
FontRenderContext frc)
{
- return new GdkFontLineMetrics (this, getFontMetrics (font), str.length ());
+ return new GdkFontLineMetrics (this, str.length ());
}
public FontMetrics getFontMetrics (Font font)
{
- // Get the font metrics through GtkToolkit to take advantage of
- // the metrics cache.
- return Toolkit.getDefaultToolkit().getFontMetrics (font);
+ if (metrics == null)
+ metrics = new GdkFontMetrics(font);
+ return metrics;
}
/**
@@ -387,4 +525,5 @@ public class GdkFontPeer extends ClasspathFontPeer
{
metricsCache.put( new Integer( glyphCode ), metrics );
}
+
}
diff --git a/gnu/java/awt/peer/gtk/GtkComponentPeer.java b/gnu/java/awt/peer/gtk/GtkComponentPeer.java
index f96033e56..f6bf588e9 100644
--- a/gnu/java/awt/peer/gtk/GtkComponentPeer.java
+++ b/gnu/java/awt/peer/gtk/GtkComponentPeer.java
@@ -90,6 +90,11 @@ public class GtkComponentPeer extends GtkGenericPeer
Insets insets;
+ /**
+ * The current repaint area.
+ */
+ protected Rectangle paintArea;
+
/* this isEnabled differs from Component.isEnabled, in that it
knows if a parent is disabled. In that case Component.isEnabled
may return true, but our isEnabled will always return false */
@@ -308,13 +313,20 @@ public class GtkComponentPeer extends GtkGenericPeer
// seems expensive. However, the graphics state does not carry
// over between calls to paint, and resetting the graphics object
// may even be more costly than simply creating a new one.
- Graphics g = getGraphics();
-
- g.setClip(event.getUpdateRect());
-
- awtComponent.paint(g);
-
- g.dispose();
+ synchronized (paintArea)
+ {
+ Graphics g = getGraphics();
+ try
+ {
+ g.setClip(paintArea);
+ awtComponent.paint(g);
+ }
+ finally
+ {
+ g.dispose();
+ paintArea = null;
+ }
+ }
}
// This method and its overrides are the only methods in the peers
@@ -327,13 +339,20 @@ public class GtkComponentPeer extends GtkGenericPeer
|| (awtComponent.getWidth() < 1 || awtComponent.getHeight() < 1))
return;
- Graphics g = getGraphics();
-
- g.setClip(event.getUpdateRect());
-
- awtComponent.update(g);
-
- g.dispose();
+ synchronized (paintArea)
+ {
+ Graphics g = getGraphics();
+ try
+ {
+ g.setClip(paintArea);
+ awtComponent.update(g);
+ }
+ finally
+ {
+ g.dispose();
+ paintArea = null;
+ }
+ }
}
public boolean isFocusTraversable ()
@@ -754,7 +773,14 @@ public class GtkComponentPeer extends GtkGenericPeer
public void coalescePaintEvent (PaintEvent e)
{
-
+ synchronized (this)
+ {
+ Rectangle newRect = e.getUpdateRect();
+ if (paintArea == null)
+ paintArea = newRect;
+ else
+ Rectangle.union(paintArea, newRect, paintArea);
+ }
}
public void updateCursorImmediately ()
diff --git a/gnu/java/awt/peer/gtk/GtkToolkit.java b/gnu/java/awt/peer/gtk/GtkToolkit.java
index d2721b43f..26724206d 100644
--- a/gnu/java/awt/peer/gtk/GtkToolkit.java
+++ b/gnu/java/awt/peer/gtk/GtkToolkit.java
@@ -62,6 +62,7 @@ import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
+import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Label;
import java.awt.List;
@@ -83,6 +84,7 @@ import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
+import java.awt.dnd.InvalidDnDOperationException;
import java.awt.dnd.peer.DragSourceContextPeer;
import java.awt.im.InputMethodHighlight;
import java.awt.image.ColorModel;
@@ -115,7 +117,6 @@ import java.awt.peer.WindowPeer;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
-import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@@ -295,7 +296,7 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit
"SansSerif" });
}
- private class LRUCache extends LinkedHashMap
+ static class LRUCache extends LinkedHashMap
{
int max_entries;
public LRUCache(int max)
@@ -310,23 +311,11 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit
}
private LRUCache fontCache = new LRUCache(50);
- private LRUCache metricsCache = new LRUCache(50);
private LRUCache imageCache = new LRUCache(50);
public FontMetrics getFontMetrics (Font font)
{
- synchronized (metricsCache)
- {
- if (metricsCache.containsKey(font))
- return (FontMetrics) metricsCache.get(font);
- }
-
- FontMetrics m = new GdkFontMetrics (font);
- synchronized (metricsCache)
- {
- metricsCache.put(font, m);
- }
- return m;
+ return ((GdkFontPeer) font.getPeer()).getFontMetrics(font);
}
public Image getImage (String filename)
@@ -431,109 +420,130 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit
protected ButtonPeer createButton (Button b)
{
+ checkHeadless();
return new GtkButtonPeer (b);
}
protected CanvasPeer createCanvas (Canvas c)
{
+ checkHeadless();
return new GtkCanvasPeer (c);
}
protected CheckboxPeer createCheckbox (Checkbox cb)
{
+ checkHeadless();
return new GtkCheckboxPeer (cb);
}
protected CheckboxMenuItemPeer createCheckboxMenuItem (CheckboxMenuItem cmi)
{
+ checkHeadless();
return new GtkCheckboxMenuItemPeer (cmi);
}
protected ChoicePeer createChoice (Choice c)
{
+ checkHeadless();
return new GtkChoicePeer (c);
}
protected DialogPeer createDialog (Dialog d)
{
+ checkHeadless();
GtkMainThread.createWindow();
return new GtkDialogPeer (d);
}
protected FileDialogPeer createFileDialog (FileDialog fd)
{
+ checkHeadless();
return new GtkFileDialogPeer (fd);
}
protected FramePeer createFrame (Frame f)
{
+ checkHeadless();
GtkMainThread.createWindow();
return new GtkFramePeer (f);
}
protected LabelPeer createLabel (Label label)
{
+ checkHeadless();
return new GtkLabelPeer (label);
}
protected ListPeer createList (List list)
{
+ checkHeadless();
return new GtkListPeer (list);
}
protected MenuPeer createMenu (Menu m)
{
+ checkHeadless();
return new GtkMenuPeer (m);
}
protected MenuBarPeer createMenuBar (MenuBar mb)
{
+ checkHeadless();
return new GtkMenuBarPeer (mb);
}
protected MenuItemPeer createMenuItem (MenuItem mi)
{
+ checkHeadless();
return new GtkMenuItemPeer (mi);
}
protected PanelPeer createPanel (Panel p)
{
+ checkHeadless();
return new GtkPanelPeer (p);
}
protected PopupMenuPeer createPopupMenu (PopupMenu target)
{
+ checkHeadless();
return new GtkPopupMenuPeer (target);
}
protected ScrollPanePeer createScrollPane (ScrollPane sp)
{
+ checkHeadless();
return new GtkScrollPanePeer (sp);
}
protected ScrollbarPeer createScrollbar (Scrollbar sb)
{
+ checkHeadless();
return new GtkScrollbarPeer (sb);
}
protected TextAreaPeer createTextArea (TextArea ta)
{
+ checkHeadless();
return new GtkTextAreaPeer (ta);
}
protected TextFieldPeer createTextField (TextField tf)
{
+ checkHeadless();
return new GtkTextFieldPeer (tf);
}
protected WindowPeer createWindow (Window w)
{
+ checkHeadless();
GtkMainThread.createWindow();
return new GtkWindowPeer (w);
}
public EmbeddedWindowPeer createEmbeddedWindow (EmbeddedWindow w)
{
+ checkHeadless();
GtkMainThread.createWindow();
return new GtkEmbeddedWindowPeer (w);
}
@@ -603,6 +613,8 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit
public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent e)
{
+ if (GraphicsEnvironment.isHeadless())
+ throw new InvalidDnDOperationException();
return new GtkDragSourceContextPeer(e);
}
@@ -612,7 +624,8 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit
int actions,
DragGestureListener l)
{
- if (recognizer.getName().equals("java.awt.dnd.MouseDragGestureRecognizer"))
+ if (recognizer.getName().equals("java.awt.dnd.MouseDragGestureRecognizer")
+ && ! GraphicsEnvironment.isHeadless())
{
GtkMouseDragGestureRecognizer gestureRecognizer
= new GtkMouseDragGestureRecognizer(ds, comp, actions, l);
@@ -672,6 +685,12 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit
|| state == Frame.MAXIMIZED_BOTH;
}
+ private void checkHeadless()
+ {
+ if (GraphicsEnvironment.isHeadless())
+ throw new HeadlessException();
+ }
+
public native int getMouseNumberOfButtons();
} // class GtkToolkit
diff --git a/gnu/java/awt/peer/headless/HeadlessGraphicsEnvironment.java b/gnu/java/awt/peer/headless/HeadlessGraphicsEnvironment.java
index 77ec4bf00..b3eeb1baa 100644
--- a/gnu/java/awt/peer/headless/HeadlessGraphicsEnvironment.java
+++ b/gnu/java/awt/peer/headless/HeadlessGraphicsEnvironment.java
@@ -46,6 +46,9 @@ import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
import java.util.Locale;
public class HeadlessGraphicsEnvironment
@@ -54,7 +57,34 @@ public class HeadlessGraphicsEnvironment
public Graphics2D createGraphics(BufferedImage image)
{
- return new RasterGraphics(image.getRaster(), image.getColorModel());
+ Graphics2D g2d;
+ try
+ {
+ // Try to get a CairoGraphics (accellerated) when available. Do this
+ // via reflection to avoid having a hard compile time dependency.
+ Class cairoSurfaceCl =
+ Class.forName("gnu.java.awt.peer.gtk.CairoSurface");
+ Raster raster = image.getRaster();
+ if (cairoSurfaceCl.isInstance(raster))
+ {
+ Method getGraphicsM = cairoSurfaceCl.getMethod("getGraphics",
+ new Class[0]);
+ g2d = (Graphics2D) getGraphicsM.invoke(raster, new Object[0]);
+ }
+ else
+ {
+ Class bigCl =
+ Class.forName("gnu.java.awt.peer.gtk.BufferedImageGraphics");
+ Constructor bigC =
+ bigCl.getConstructor(new Class[]{BufferedImage.class });
+ g2d = (Graphics2D) bigC.newInstance(new Object[]{ image});
+ }
+ }
+ catch (Exception ex)
+ {
+ g2d = new RasterGraphics(image.getRaster(), image.getColorModel());
+ }
+ return g2d;
}
public Font[] getAllFonts()
diff --git a/gnu/java/awt/peer/swing/SwingComponentPeer.java b/gnu/java/awt/peer/swing/SwingComponentPeer.java
index 7e1ad86a0..bfa14ddde 100644
--- a/gnu/java/awt/peer/swing/SwingComponentPeer.java
+++ b/gnu/java/awt/peer/swing/SwingComponentPeer.java
@@ -64,9 +64,6 @@ import java.awt.image.VolatileImage;
import java.awt.peer.ComponentPeer;
import java.awt.peer.ContainerPeer;
import java.awt.peer.LightweightPeer;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
import javax.swing.JComponent;
import javax.swing.RepaintManager;
@@ -83,7 +80,7 @@ import javax.swing.RepaintManager;
* This class also provides the necesary hooks into the Swing painting and
* event handling system. In order to achieve this, it traps paint, mouse and
* key events in {@link #handleEvent(AWTEvent)} and calls some special methods
- * ({@link #peerPaint(Graphics,boolean)}, {@link #handleKeyEvent(KeyEvent)},
+ * ({@link #peerPaint(Graphics)}, {@link #handleKeyEvent(KeyEvent)},
* {@link #handleMouseEvent(MouseEvent)} and
* {@link #handleMouseMotionEvent(MouseEvent)}) that call the corresponding
* Swing methods.
@@ -110,25 +107,18 @@ public class SwingComponentPeer
protected Font peerFont;
/**
- * The repaint requests that will be handled next. The events queued
- * up here are in the exact same order as they appear in
- * {@link #coalescePaintEvent(PaintEvent)}, that is in event queue order.
- * This is used for coalescing paint events.
- *
- * @see #coalescePaintEvent(PaintEvent)
+ * The current repaint area.
*/
- protected List currentPaintEvents;
+ protected Rectangle paintArea;
/**
* Creates a SwingComponentPeer instance. Subclasses are expected to call
- * this constructor and thereafter call {@link #init(Component,
- * SwingComponent)}in order to setup the AWT and Swing components properly.
+ * this constructor and thereafter call {@link #init(Component, JComponent)}
+ * in order to setup the AWT and Swing components properly.
*/
protected SwingComponentPeer()
{
- // Initialize paint event queue.
- currentPaintEvents = new LinkedList();
-
+ // Nothing to do here.
}
/**
@@ -412,24 +402,22 @@ public class SwingComponentPeer
// paint event list.
// We must synchronize on the tree lock first to avoid deadlock,
// because Container.paint() will grab it anyway.
- synchronized (awtComponent.getTreeLock())
+ synchronized (this)
{
- synchronized (currentPaintEvents)
+ assert paintArea != null;
+ if (awtComponent.isShowing())
{
- if (currentPaintEvents.contains(e))
+ Graphics g = awtComponent.getGraphics();
+ try
{
- Graphics g = awtComponent.getGraphics();
- try
- {
- Rectangle clip = ((PaintEvent) e).getUpdateRect();
- g.clipRect(clip.x, clip.y, clip.width, clip.height);
- peerPaint(g, e.getID() == PaintEvent.UPDATE);
- }
- finally
- {
- g.dispose();
- }
- currentPaintEvents.remove(e);
+ Rectangle clip = paintArea;
+ g.clipRect(clip.x, clip.y, clip.width, clip.height);
+ peerPaint(g, e.getID() == PaintEvent.UPDATE);
+ }
+ finally
+ {
+ g.dispose();
+ paintArea = null;
}
}
}
@@ -832,35 +820,13 @@ public class SwingComponentPeer
*/
public void coalescePaintEvent(PaintEvent e)
{
- synchronized (currentPaintEvents)
+ synchronized (this)
{
Rectangle newRect = e.getUpdateRect();
- boolean coalesced = false;
- for (Iterator i = currentPaintEvents.iterator(); i.hasNext() && ! coalesced;)
- {
- PaintEvent e2 = (PaintEvent) i.next();
- if (e.getID() == e2.getID())
- {
- Rectangle oldRect = e2.getUpdateRect();
- if (oldRect.contains(newRect))
- {
- // Merge newRect into oldRect. We have to discard the old request
- // so that the events are still in the correct order.
- i.remove();
- newRect.setBounds(oldRect);
- coalesced = true;
- }
- else if (newRect.contains(oldRect))
- {
- // Merge oldRect into newRect. We have to discard the old request
- // so that the events are still in the correct order.
- i.remove();
- coalesced = true;
- }
- }
- // TODO: Maybe do something more clever here.
- }
- currentPaintEvents.add(e);
+ if (paintArea == null)
+ paintArea = newRect;
+ else
+ Rectangle.union(paintArea, newRect, paintArea);
}
}
diff --git a/gnu/java/net/PlainSocketImpl.java b/gnu/java/net/PlainSocketImpl.java
index 64e498746..5bda0a5e9 100644
--- a/gnu/java/net/PlainSocketImpl.java
+++ b/gnu/java/net/PlainSocketImpl.java
@@ -342,6 +342,9 @@ public class PlainSocketImpl extends SocketImpl
that.impl.getState().setChannelFD(c.getState());
that.channel = new SocketChannelImpl(c);
that.setOption(SO_REUSEADDR, Boolean.TRUE);
+ // Reset the inherited timeout.
+ that.setOption(SO_TIMEOUT, Integer.valueOf(0));
+
}
/**
diff --git a/gnu/java/util/regex/RE.java b/gnu/java/util/regex/RE.java
index 94aa0142c..09ff74b90 100644
--- a/gnu/java/util/regex/RE.java
+++ b/gnu/java/util/regex/RE.java
@@ -130,7 +130,11 @@ public class RE extends REToken {
private static final String VERSION = "1.1.5-dev";
// The localized strings are kept in a separate file
- private static ResourceBundle messages = PropertyResourceBundle.getBundle("gnu/java/util/regex/MessagesBundle", Locale.getDefault());
+ // Used by getLocalizedMessage().
+ private static ResourceBundle messages;
+
+ // Name of the bundle that contains the localized messages.
+ private static final String bundle = "gnu/java/util/regex/MessagesBundle";
// These are, respectively, the first and last tokens in our linked list
// If there is only one token, firstToken == lastToken
@@ -266,6 +270,8 @@ public class RE extends REToken {
// Retrieves a message from the ResourceBundle
static final String getLocalizedMessage(String key) {
+ if (messages == null)
+ messages = PropertyResourceBundle.getBundle(bundle, Locale.getDefault());
return messages.getString(key);
}
diff --git a/gnu/java/util/regex/RESyntax.java b/gnu/java/util/regex/RESyntax.java
index b66b32f58..db11e2db4 100644
--- a/gnu/java/util/regex/RESyntax.java
+++ b/gnu/java/util/regex/RESyntax.java
@@ -54,8 +54,6 @@ import java.util.BitSet;
public final class RESyntax implements Serializable {
static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator");
- private static final String SYNTAX_IS_FINAL = RE.getLocalizedMessage("syntax.final");
-
private BitSet bits;
// true for the constant defined syntaxes
@@ -513,7 +511,8 @@ public final class RESyntax implements Serializable {
* @return a reference to this object for easy chaining.
*/
public RESyntax set(int index) {
- if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL);
+ if (isFinal)
+ throw new IllegalAccessError(RE.getLocalizedMessage("syntax.final"));
bits.set(index);
return this;
}
@@ -525,7 +524,8 @@ public final class RESyntax implements Serializable {
* @return a reference to this object for easy chaining.
*/
public RESyntax clear(int index) {
- if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL);
+ if (isFinal)
+ throw new IllegalAccessError(RE.getLocalizedMessage("syntax.final"));
bits.clear(index);
return this;
}
@@ -548,7 +548,8 @@ public final class RESyntax implements Serializable {
* @return this object for convenient chaining
*/
public RESyntax setLineSeparator(String aSeparator) {
- if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL);
+ if (isFinal)
+ throw new IllegalAccessError(RE.getLocalizedMessage("syntax.final"));
lineSeparator = aSeparator;
return this;
}
diff --git a/gnu/javax/management/Server.java b/gnu/javax/management/Server.java
new file mode 100644
index 000000000..049b0e360
--- /dev/null
+++ b/gnu/javax/management/Server.java
@@ -0,0 +1,2163 @@
+/* Server.java -- A GNU Classpath management server.
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package gnu.javax.management;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.io.StreamCorruptedException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.BadAttributeValueExpException;
+import javax.management.BadBinaryOpValueExpException;
+import javax.management.BadStringOperationException;
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.IntrospectionException;
+import javax.management.InvalidApplicationException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MalformedObjectNameException;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanPermission;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerDelegate;
+import javax.management.MBeanTrustPermission;
+import javax.management.NotCompliantMBeanException;
+import javax.management.Notification;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationEmitter;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.management.OperationsException;
+import javax.management.QueryExp;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+import javax.management.StandardMBean;
+
+import javax.management.loading.ClassLoaderRepository;
+
+/**
+ * This class provides an {@link javax.management.MBeanServer}
+ * implementation for GNU Classpath.
+ *
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ * @since 1.5
+ */
+public class Server
+ implements MBeanServer
+{
+
+ /**
+ * The registered beans, represented as a map of
+ * {@link javax.management.ObjectName}s to
+ * {@link java.lang.Object}s.
+ */
+ private Map beans;
+
+ /**
+ * The default domain.
+ */
+ private String defaultDomain;
+
+ /**
+ * The outer server.
+ */
+ private MBeanServer outer;
+
+ /**
+ * The delegate bean.
+ */
+ private MBeanServerDelegate delegate;
+
+ /**
+ * The class loader repository.
+ */
+ private ClassLoaderRepository repository;
+
+ /**
+ * The map of listener delegates to the true
+ * listener.
+ */
+ private Map listeners;
+
+ /**
+ * Constructs a new management server using the specified
+ * default domain, delegate bean and outer server.
+ *
+ * @param domain the default domain to use for beans constructed
+ * with no specified domain.
+ * @param outer an {@link javax.management.MBeanServer} to pass
+ * to beans implementing the {@link MBeanRegistration}
+ * interface, or <code>null</code> if <code>this</code>
+ * should be passed.
+ * @param delegate the delegate bean for this server.
+ */
+ public Server(String defaultDomain, MBeanServer outer,
+ MBeanServerDelegate delegate)
+ {
+ this.defaultDomain = defaultDomain;
+ this.outer = outer;
+ this.delegate = delegate;
+ }
+
+ /**
+ * Checks for the necessary security privileges to perform an
+ * operation.
+ *
+ * @param name the name of the bean being accessed.
+ * @param member the name of the operation or attribute being
+ * accessed, or <code>null</code> if one is not
+ * involved.
+ * @param action the action being performed.
+ * @throws SecurityException if the action is denied.
+ */
+ private void checkSecurity(ObjectName name, String member,
+ String action)
+ {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ try
+ {
+ MBeanInfo info = null;
+ if (name != null)
+ {
+ Object bean = getBean(name);
+ Method method = bean.getClass().getMethod("getMBeanInfo", null);
+ info = (MBeanInfo) method.invoke(bean, null);
+ }
+ sm.checkPermission(new MBeanPermission((info == null) ?
+ null : info.getClassName(),
+ member, name, action));
+ }
+ catch (InstanceNotFoundException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to get bean.").initCause(e));
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to get bean info.").initCause(e));
+ }
+ catch (IllegalAccessException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to get bean info.").initCause(e));
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to get bean info.").initCause(e));
+ }
+ catch (InvocationTargetException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to get bean info.").initCause(e));
+ }
+ }
+
+ /**
+ * Retrieves the specified bean.
+ *
+ * @param name the name of the bean.
+ * @return the bean.
+ * @throws InstanceNotFoundException if the name of the management bean
+ * could not be resolved.
+ */
+ private Object getBean(ObjectName name)
+ throws InstanceNotFoundException
+ {
+ ServerInfo bean = (ServerInfo) beans.get(name);
+ if (bean == null)
+ throw new InstanceNotFoundException("The bean, " + name +
+ ", was not found.");
+ return bean.getObject();
+ }
+
+ /**
+ * Registers the supplied listener with the specified management
+ * bean. Notifications emitted by the management bean are forwarded
+ * to the listener via the server, which will convert an MBean
+ * references in the source to a portable {@link ObjectName}
+ * instance. The notification is otherwise unchanged.
+ *
+ * @param name the name of the management bean with which the listener
+ * should be registered.
+ * @param listener the listener which will handle notifications from
+ * the bean.
+ * @param filter the filter to apply to incoming notifications, or
+ * <code>null</code> if no filtering should be applied.
+ * @param passback an object to be passed to the listener when a
+ * notification is emitted.
+ * @throws InstanceNotFoundException if the name of the management bean
+ * could not be resolved.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "addNotificationListener")</code>}.
+ * @see #removeNotificationListener(ObjectName, NotificationListener)
+ * @see #removeNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object)
+ * @see NotificationBroadcaster#addNotificationListener(NotificationListener,
+ * NotificationFilter,
+ * Object)
+ */
+ public void addNotificationListener(ObjectName name, NotificationListener listener,
+ NotificationFilter filter, Object passback)
+ throws InstanceNotFoundException
+ {
+ Object bean = getBean(name);
+ checkSecurity(name, null, "addNotificationListener");
+ if (bean instanceof NotificationBroadcaster)
+ {
+ NotificationBroadcaster bbean = (NotificationBroadcaster) bean;
+ if (listeners == null)
+ listeners = new HashMap();
+ NotificationListener indirection = new ServerNotificationListener(bean, name,
+ listener);
+ bbean.addNotificationListener(indirection, filter, passback);
+ listeners.put(listener, indirection);
+ }
+ }
+
+ /**
+ * <p>
+ * Registers the supplied listener with the specified management
+ * bean. Notifications emitted by the management bean are forwarded
+ * to the listener via the server, which will convert any MBean
+ * references in the source to portable {@link ObjectName}
+ * instances. The notification is otherwise unchanged.
+ * </p>
+ * <p>
+ * The listener that receives notifications will be the one that is
+ * registered with the given name at the time this method is called.
+ * Even if it later unregisters and ceases to use that name, it will
+ * still receive notifications.
+ * </p>
+ *
+ * @param name the name of the management bean with which the listener
+ * should be registered.
+ * @param listener the name of the listener which will handle
+ * notifications from the bean.
+ * @param filter the filter to apply to incoming notifications, or
+ * <code>null</code> if no filtering should be applied.
+ * @param passback an object to be passed to the listener when a
+ * notification is emitted.
+ * @throws InstanceNotFoundException if the name of the management bean
+ * could not be resolved.
+ * @throws RuntimeOperationsException if the bean associated with the given
+ * object name is not a
+ * {@link NotificationListener}. This
+ * exception wraps an
+ * {@link IllegalArgumentException}.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "addNotificationListener")</code>}.
+ * @see #removeNotificationListener(ObjectName, NotificationListener)
+ * @see #removeNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object)
+ * @see NotificationBroadcaster#addNotificationListener(NotificationListener,
+ * NotificationFilter,
+ * Object)
+ */
+ public void addNotificationListener(ObjectName name, ObjectName listener,
+ NotificationFilter filter, Object passback)
+ throws InstanceNotFoundException
+ {
+ Object lbean = getBean(listener);
+ if (!(lbean instanceof NotificationListener))
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The supplied listener name does not " +
+ "correspond to a notification listener.");
+ throw new RuntimeOperationsException(e);
+ }
+ addNotificationListener(name, ((NotificationListener) lbean), filter, passback);
+ }
+
+ /**
+ * <p>
+ * Instantiates a new instance of the specified management bean
+ * using the default constructor and registers it with the server
+ * under the supplied name. The class is loaded using the
+ * {@link javax.management.loading.ClassLoaderRepository default
+ * loader repository} of the server.
+ * </p>
+ * <p>
+ * If the name supplied is <code>null</code>, then the bean is
+ * expected to implement the {@link MBeanRegistration} interface.
+ * The {@link MBeanRegistration#preRegister preRegister} method
+ * of this interface will be used to obtain the name in this case.
+ * </p>
+ * <p>
+ * This method is equivalent to calling {@link
+ * #createMBean(String, ObjectName, Object[], String[])
+ * <code>createMBean(className, name, (Object[]) null,
+ * (String[]) null)</code>} with <code>null</code> parameters
+ * and signature.
+ * </p>
+ *
+ * @param className the class of the management bean, of which
+ * an instance should be created.
+ * @param name the name to register the new bean with.
+ * @return an {@link ObjectInstance} containing the {@link ObjectName}
+ * and Java class name of the created instance.
+ * @throws ReflectionException if an exception occurs in creating
+ * an instance of the bean.
+ * @throws InstanceAlreadyExistsException if a matching instance
+ * already exists.
+ * @throws MBeanRegistrationException if an exception occurs in
+ * calling the preRegister
+ * method.
+ * @throws MBeanException if the bean's constructor throws an exception.
+ * @throws NotCompliantMBeanException if the created bean is not
+ * compliant with the JMX specification.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> class name or object
+ * name or if the object name is a pattern.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply the
+ * use of the <code>instantiate</code>
+ * and <code>registerMBean</code> methods.
+ * @see #createMBean(String, ObjectName, Object[], String[])
+ */
+ public ObjectInstance createMBean(String className, ObjectName name)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException
+ {
+ return createMBean(className, name, (Object[]) null, (String[]) null);
+ }
+
+ /**
+ * <p>
+ * Instantiates a new instance of the specified management bean
+ * using the given constructor and registers it with the server
+ * under the supplied name. The class is loaded using the
+ * {@link javax.management.loading.ClassLoaderRepository default
+ * loader repository} of the server.
+ * </p>
+ * <p>
+ * If the name supplied is <code>null</code>, then the bean is
+ * expected to implement the {@link MBeanRegistration} interface.
+ * The {@link MBeanRegistration#preRegister preRegister} method
+ * of this interface will be used to obtain the name in this case.
+ * </p>
+ *
+ * @param className the class of the management bean, of which
+ * an instance should be created.
+ * @param name the name to register the new bean with.
+ * @param params the parameters for the bean's constructor.
+ * @param sig the signature of the constructor to use.
+ * @return an {@link ObjectInstance} containing the {@link ObjectName}
+ * and Java class name of the created instance.
+ * @throws ReflectionException if an exception occurs in creating
+ * an instance of the bean.
+ * @throws InstanceAlreadyExistsException if a matching instance
+ * already exists.
+ * @throws MBeanRegistrationException if an exception occurs in
+ * calling the preRegister
+ * method.
+ * @throws MBeanException if the bean's constructor throws an exception.
+ * @throws NotCompliantMBeanException if the created bean is not
+ * compliant with the JMX specification.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> class name or object
+ * name or if the object name is a pattern.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply the
+ * use of the <code>instantiate</code>
+ * and <code>registerMBean</code> methods.
+ */
+ public ObjectInstance createMBean(String className, ObjectName name,
+ Object[] params, String[] sig)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException
+ {
+ return registerMBean(instantiate(className, params, sig), name);
+ }
+
+ /**
+ * <p>
+ * Instantiates a new instance of the specified management bean
+ * using the default constructor and registers it with the server
+ * under the supplied name. The class is loaded using the
+ * given class loader. If this argument is <code>null</code>,
+ * then the same class loader as was used to load the server
+ * is used.
+ * </p>
+ * <p>
+ * If the name supplied is <code>null</code>, then the bean is
+ * expected to implement the {@link MBeanRegistration} interface.
+ * The {@link MBeanRegistration#preRegister preRegister} method
+ * of this interface will be used to obtain the name in this case.
+ * </p>
+ * <p>
+ * This method is equivalent to calling {@link
+ * #createMBean(String, ObjectName, ObjectName, Object[], String)
+ * <code>createMBean(className, name, loaderName, (Object[]) null,
+ * (String) null)</code>} with <code>null</code> parameters
+ * and signature.
+ * </p>
+ *
+ * @param className the class of the management bean, of which
+ * an instance should be created.
+ * @param name the name to register the new bean with.
+ * @param loaderName the name of the class loader.
+ * @return an {@link ObjectInstance} containing the {@link ObjectName}
+ * and Java class name of the created instance.
+ * @throws ReflectionException if an exception occurs in creating
+ * an instance of the bean.
+ * @throws InstanceAlreadyExistsException if a matching instance
+ * already exists.
+ * @throws MBeanRegistrationException if an exception occurs in
+ * calling the preRegister
+ * method.
+ * @throws MBeanException if the bean's constructor throws an exception.
+ * @throws NotCompliantMBeanException if the created bean is not
+ * compliant with the JMX specification.
+ * @throws InstanceNotFoundException if the specified class loader is not
+ * registered with the server.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> class name or object
+ * name or if the object name is a pattern.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply the
+ * use of the <code>instantiate</code>
+ * and <code>registerMBean</code> methods.
+ * @see #createMBean(String, ObjectName, ObjectName, Object[], String[])
+ */
+ public ObjectInstance createMBean(String className, ObjectName name,
+ ObjectName loaderName)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException
+ {
+ return createMBean(className, name, loaderName, (Object[]) null,
+ (String[]) null);
+ }
+
+ /**
+ * <p>
+ * Instantiates a new instance of the specified management bean
+ * using the given constructor and registers it with the server
+ * under the supplied name. The class is loaded using the
+ * given class loader. If this argument is <code>null</code>,
+ * then the same class loader as was used to load the server
+ * is used.
+ * </p>
+ * <p>
+ * If the name supplied is <code>null</code>, then the bean is
+ * expected to implement the {@link MBeanRegistration} interface.
+ * The {@link MBeanRegistration#preRegister preRegister} method
+ * of this interface will be used to obtain the name in this case.
+ * </p>
+ *
+ * @param className the class of the management bean, of which
+ * an instance should be created.
+ * @param name the name to register the new bean with.
+ * @param loaderName the name of the class loader.
+ * @param params the parameters for the bean's constructor.
+ * @param sig the signature of the constructor to use.
+ * @return an {@link ObjectInstance} containing the {@link ObjectName}
+ * and Java class name of the created instance.
+ * @throws ReflectionException if an exception occurs in creating
+ * an instance of the bean.
+ * @throws InstanceAlreadyExistsException if a matching instance
+ * already exists.
+ * @throws MBeanRegistrationException if an exception occurs in
+ * calling the preRegister
+ * method.
+ * @throws MBeanException if the bean's constructor throws an exception.
+ * @throws NotCompliantMBeanException if the created bean is not
+ * compliant with the JMX specification.
+ * @throws InstanceNotFoundException if the specified class loader is not
+ * registered with the server.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> class name or object
+ * name or if the object name is a pattern.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply the
+ * use of the <code>instantiate</code>
+ * and <code>registerMBean</code> methods.
+ */
+ public ObjectInstance createMBean(String className, ObjectName name,
+ ObjectName loaderName, Object[] params,
+ String[] sig)
+ throws ReflectionException, InstanceAlreadyExistsException,
+ MBeanRegistrationException, MBeanException,
+ NotCompliantMBeanException, InstanceNotFoundException
+ {
+ return registerMBean(instantiate(className, loaderName, params, sig),
+ name);
+ }
+
+ /**
+ * Deserializes a byte array using the class loader of the specified
+ * management bean as its context.
+ *
+ * @param name the name of the bean whose class loader should be used.
+ * @param data the byte array to be deserialized.
+ * @return the deserialized object stream.
+ * @deprecated {@link #getClassLoaderFor(ObjectName)} should be used
+ * to obtain the class loader of the bean, which can then
+ * be used to perform deserialization in the user's code.
+ * @throws InstanceNotFoundException if the specified bean is not
+ * registered with the server.
+ * @throws OperationsException if any I/O error is thrown by the
+ * deserialization process.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "getClassLoaderFor")</code>
+ */
+ public ObjectInputStream deserialize(ObjectName name, byte[] data)
+ throws InstanceNotFoundException, OperationsException
+ {
+ try
+ {
+ return new ServerInputStream(new ByteArrayInputStream(data),
+ getClassLoaderFor(name));
+ }
+ catch (IOException e)
+ {
+ throw new OperationsException("An I/O error occurred: " + e);
+ }
+ }
+
+ /**
+ * Deserializes a byte array using the same class loader for its context
+ * as was used to load the given class. This class loader is obtained by
+ * loading the specified class using the {@link
+ * javax.management.loading.ClassLoaderRepository Class Loader Repository}
+ * and then using the class loader of the resulting {@link Class} instance.
+ *
+ * @param name the name of the class which should be loaded to obtain the
+ * class loader.
+ * @param data the byte array to be deserialized.
+ * @return the deserialized object stream.
+ * @deprecated {@link #getClassLoaderRepository} should be used
+ * to obtain the class loading repository, which can then
+ * be used to obtain the {@link Class} instance and deserialize
+ * the array using its class loader.
+ * @throws OperationsException if any I/O error is thrown by the
+ * deserialization process.
+ * @throws ReflectionException if an error occurs in obtaining the
+ * {@link Class} instance.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(null, null, null,
+ * "getClassLoaderRepository")</code>
+ */
+ public ObjectInputStream deserialize(String name, byte[] data)
+ throws OperationsException, ReflectionException
+ {
+ try
+ {
+ Class c = getClassLoaderRepository().loadClass(name);
+ return new ServerInputStream(new ByteArrayInputStream(data),
+ c.getClassLoader());
+ }
+ catch (IOException e)
+ {
+ throw new OperationsException("An I/O error occurred: " + e);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new ReflectionException(e, "The class could not be found.");
+ }
+ }
+
+ /**
+ * Deserializes a byte array using the same class loader for its context
+ * as was used to load the given class. The name of the class loader to
+ * be used is supplied, and may be <code>null</code> if the server's
+ * class loader should be used instead.
+ *
+ * @param name the name of the class which should be loaded to obtain the
+ * class loader.
+ * @param loader the name of the class loader to use, or <code>null</code>
+ * if the class loader of the server should be used.
+ * @param data the byte array to be deserialized.
+ * @return the deserialized object stream.
+ * @deprecated {@link #getClassLoader(ObjectName} can be used to obtain
+ * the named class loader and deserialize the array.
+ * @throws InstanceNotFoundException if the specified class loader is not
+ * registered with the server.
+ * @throws OperationsException if any I/O error is thrown by the
+ * deserialization process.
+ * @throws ReflectionException if an error occurs in obtaining the
+ * {@link Class} instance.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, loader,
+ * "getClassLoader")</code>
+ */
+ public ObjectInputStream deserialize(String name, ObjectName loader, byte[] data)
+ throws InstanceNotFoundException, ReflectionException,
+ OperationsException
+ {
+ try
+ {
+ Class c = getClassLoader(loader).loadClass(name);
+ return new ServerInputStream(new ByteArrayInputStream(data),
+ c.getClassLoader());
+ }
+ catch (IOException e)
+ {
+ throw new OperationsException("An I/O error occurred: " + e);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new ReflectionException(e, "The class could not be found.");
+ }
+ }
+
+ /**
+ * Returns the value of the supplied attribute from the specified
+ * management bean.
+ *
+ * @param bean the bean to retrieve the value from.
+ * @param name the name of the attribute to retrieve.
+ * @return the value of the attribute.
+ * @throws AttributeNotFoundException if the attribute could not be
+ * accessed from the bean.
+ * @throws MBeanException if the management bean's accessor throws
+ * an exception.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws ReflectionException if an exception was thrown in trying
+ * to invoke the bean's accessor.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> bean or attribute
+ * name.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, name, bean,
+ * "getAttribute")</code>}.
+ * @see DynamicMBean#getAttribute(String)
+ */
+ public Object getAttribute(ObjectName bean, String name)
+ throws MBeanException, AttributeNotFoundException,
+ InstanceNotFoundException, ReflectionException
+ {
+ if (bean == null || name == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("One of the supplied arguments was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ Object abean = getBean(bean);
+ checkSecurity(bean, name, "getAttribute");
+ if (abean instanceof DynamicMBean)
+ return ((DynamicMBean) abean).getAttribute(name);
+ else
+ try
+ {
+ return new StandardMBean(abean, null).getAttribute(name);
+ }
+ catch (NotCompliantMBeanException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to create dynamic bean.").initCause(e));
+ }
+ }
+
+
+ /**
+ * Returns the values of the named attributes from the specified
+ * management bean.
+ *
+ * @param bean the bean to retrieve the value from.
+ * @param names the names of the attributes to retrieve.
+ * @return the values of the attributes.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws ReflectionException if an exception was thrown in trying
+ * to invoke the bean's accessor.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> bean or attribute
+ * name.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, bean,
+ * "getAttribute")</code>}. Additionally,
+ * for an attribute name, <code>n</code>, the
+ * caller's permission must imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, n, bean,
+ * "getAttribute")</code>} or that attribute will
+ * not be included.
+ *
+ * @see DynamicMBean#getAttributes(String[])
+ */
+ public AttributeList getAttributes(ObjectName bean, String[] names)
+ throws InstanceNotFoundException, ReflectionException
+ {
+ if (bean == null || names == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("One of the supplied arguments was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ Object abean = getBean(bean);
+ checkSecurity(bean, null, "getAttribute");
+ AttributeList list = new AttributeList(names.length);
+ for (int a = 0; a < names.length; ++a)
+ {
+ if (names[a] == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("Argument " + a + " was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ checkSecurity(bean, names[a], "getAttribute");
+ try
+ {
+ Object value;
+ if (abean instanceof DynamicMBean)
+ value = ((DynamicMBean) abean).getAttribute(names[a]);
+ else
+ try
+ {
+ value = new StandardMBean(abean, null).getAttribute(names[a]);
+ }
+ catch (NotCompliantMBeanException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to create dynamic bean.").initCause(e));
+ }
+ list.add(new Attribute(names[a], value));
+ }
+ catch (AttributeNotFoundException e)
+ {
+ /* Ignored */
+ }
+ catch (MBeanException e)
+ {
+ /* Ignored */
+ }
+ }
+ return list;
+ }
+
+
+ /**
+ * Returns the specified class loader. If the specified value is
+ * <code>null</code>, then the class loader of the server will be
+ * returned. If <code>l</code> is the requested class loader,
+ * and <code>r</code> is the actual class loader returned, then
+ * either <code>l</code> and <code>r</code> will be identical,
+ * or they will at least return the same class from
+ * {@link ClassLoader#loadClass(String)} for any given string.
+ * They may not be identical due to one or the other
+ * being wrapped in another class loader (e.g. for security).
+ *
+ * @param name the name of the class loader to return.
+ * @return the class loader.
+ * @throws InstanceNotFoundException if the class loader can not
+ * be found.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "getClassLoader")</code>
+ */
+ public ClassLoader getClassLoader(ObjectName name)
+ throws InstanceNotFoundException
+ {
+ if (name == null)
+ {
+ checkSecurity(null, null, "getClassLoader");
+ return getClass().getClassLoader();
+ }
+ Object bean = getBean(name);
+ checkSecurity(name, null, "getClassLoader");
+ return (ClassLoader) bean;
+ }
+
+ /**
+ * Returns the class loader of the specified management bean. If
+ * <code>l</code> is the requested class loader, and <code>r</code>
+ * is the actual class loader returned, then either <code>l</code>
+ * and <code>r</code> will be identical, or they will at least
+ * return the same class from {@link ClassLoader#loadClass(String)}
+ * for any given string. They may not be identical due to one or
+ * the other being wrapped in another class loader (e.g. for
+ * security).
+ *
+ * @param name the name of the bean whose class loader should be
+ * returned.
+ * @return the class loader.
+ * @throws InstanceNotFoundException if the bean is not registered
+ * with the server.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "getClassLoaderFor")</code>
+ */
+ public ClassLoader getClassLoaderFor(ObjectName name)
+ throws InstanceNotFoundException
+ {
+ Object bean = getBean(name);
+ checkSecurity(name, null, "getClassLoaderFor");
+ return bean.getClass().getClassLoader();
+ }
+
+ /**
+ * Returns the class loader repository used by this server.
+ *
+ * @return the class loader repository.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(null, null, null,
+ * "getClassLoaderRepository")</code>
+ */
+ public ClassLoaderRepository getClassLoaderRepository()
+ {
+ return repository;
+ }
+
+ /**
+ * Returns the default domain this server applies to beans that have
+ * no specified domain.
+ *
+ * @return the default domain.
+ */
+ public String getDefaultDomain()
+ {
+ return defaultDomain;
+ }
+
+
+ /**
+ * Returns an array containing all the domains used by beans registered
+ * with this server. The ordering of the array is undefined.
+ *
+ * @return the list of domains.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(null, null, name,
+ * "getDomains")</code>}. Additionally,
+ * for an domain, <code>d</code>, the
+ * caller's permission must imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(null, null,
+ * new ObjectName("d:x=x"), "getDomains")</code>}
+ * or that domain will not be included. Note
+ * that "x=x" is an arbitrary key-value pair
+ * provided to satisfy the constructor.
+ * @see ObjectName#getDomain()
+ */
+ public String[] getDomains()
+ {
+ checkSecurity(null, null, "getDomains");
+ Set domains = new HashSet();
+ Iterator iterator = beans.keySet().iterator();
+ while (iterator.hasNext())
+ {
+ String d = ((ObjectName) iterator.next()).getDomain();
+ try
+ {
+ checkSecurity(new ObjectName(d + ":x=x"), null, "getDomains");
+ domains.add(d);
+ }
+ catch (MalformedObjectNameException e)
+ {
+ /* Ignored */
+ }
+ }
+ return (String[]) domains.toArray(new String[domains.size()]);
+ }
+
+ /**
+ * Returns the number of management beans registered with this server.
+ * This may be less than the real number if the caller's access is
+ * restricted.
+ *
+ * @return the number of registered beans.
+ */
+ public Integer getMBeanCount()
+ {
+ return Integer.valueOf(beans.size());
+ }
+
+ /**
+ * Returns information on the given management bean.
+ *
+ * @param name the name of the management bean.
+ * @return an instance of {@link MBeanInfo} for the bean.
+ * @throws IntrospectionException if an exception occurs in examining
+ * the bean.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws ReflectionException if an exception occurs when trying
+ * to invoke {@link DynamicMBean#getMBeanInfo()}
+ * on the bean.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "getMBeanInfo")</code>}.
+ * @see DynamicMBean#getMBeanInfo()
+ */
+ public MBeanInfo getMBeanInfo(ObjectName name)
+ throws InstanceNotFoundException, IntrospectionException,
+ ReflectionException
+ {
+ Object bean = getBean(name);
+ checkSecurity(name, null, "getMBeanInfo");
+ try
+ {
+ Method method = bean.getClass().getMethod("getMBeanInfo", null);
+ return (MBeanInfo) method.invoke(bean, null);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new IntrospectionException("The getMBeanInfo method " +
+ "could not be found.");
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new ReflectionException(e, "Failed to call getMBeanInfo");
+ }
+ catch (IllegalArgumentException e)
+ {
+ throw new ReflectionException(e, "Failed to call getMBeanInfo");
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new ReflectionException(e, "The method threw an exception");
+ }
+ }
+
+ /**
+ * Returns the {@link ObjectInstance} created for the specified
+ * management bean on registration.
+ *
+ * @param name the name of the bean.
+ * @return the corresponding {@link ObjectInstance} instance.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "getObjectInstance")</code>
+ * @see #createMBean(String, ObjectName)
+ */
+ public ObjectInstance getObjectInstance(ObjectName name)
+ throws InstanceNotFoundException
+ {
+ ServerInfo bean = (ServerInfo) beans.get(name);
+ if (bean == null)
+ throw new InstanceNotFoundException("The bean, " + name +
+ ", was not found.");
+ return bean.getInstance();
+ }
+
+ /**
+ * <p>
+ * Creates an instance of the specified class using the list of
+ * class loaders from the {@link
+ * javax.management.loading.ClassLoaderRepository Class Loader
+ * Repository}. The class should have a public constructor
+ * with no arguments. A reference to the new instance is returned,
+ * but the instance is not yet registered with the server.
+ * </p>
+ * <p>
+ * This method is equivalent to calling {@link
+ * #instantiate(String, Object[], String[])
+ * <code>instantiate(name, (Object[]) null, (String[]) null)</code>}
+ * with <code>null</code> parameters and signature.
+ * </p>
+ *
+ * @param name the name of the class of bean to be instantiated.
+ * @return an instance of the given class.
+ * @throws ReflectionException if an exception is thrown during
+ * loading the class or calling the
+ * constructor.
+ * @throws MBeanException if the constructor throws an exception.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> name.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, null,
+ * "instantiate")</code>}.
+ * @see #instantiate(String, Object[], String[])
+ */
+ public Object instantiate(String name)
+ throws ReflectionException, MBeanException
+ {
+ return instantiate(name, (Object[]) null, (String[]) null);
+ }
+
+ /**
+ * Creates an instance of the specified class using the list of
+ * class loaders from the {@link
+ * javax.management.loading.ClassLoaderRepository Class Loader
+ * Repository}. The class should have a public constructor
+ * matching the supplied signature. A reference to the new
+ * instance is returned, but the instance is not yet
+ * registered with the server.
+ *
+ * @param name the name of the class of bean to be instantiated.
+ * @param params the parameters for the constructor.
+ * @param sig the signature of the constructor.
+ * @return an instance of the given class.
+ * @throws ReflectionException if an exception is thrown during
+ * loading the class or calling the
+ * constructor.
+ * @throws MBeanException if the constructor throws an exception.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> name.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, null,
+ * "instantiate")</code>}.
+ */
+ public Object instantiate(String name, Object[] params, String[] sig)
+ throws ReflectionException, MBeanException
+ {
+ checkSecurity(null, null, "instantiate");
+ if (name == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The name was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ Class[] sigTypes = new Class[sig.length];
+ for (int a = 0; a < sigTypes.length; ++a)
+ {
+ try
+ {
+ sigTypes[a] = repository.loadClass(sig[a]);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new ReflectionException(e, "The class, " + sigTypes[a] +
+ ", in the method signature " +
+ "could not be loaded.");
+ }
+ }
+ try
+ {
+ Constructor cons =
+ repository.loadClass(name).getConstructor(sigTypes);
+ return cons.newInstance(params);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new ReflectionException(e, "The class, " + name +
+ ", of the constructor " +
+ "could not be loaded.");
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new ReflectionException(e, "The method, " + name +
+ ", could not be found.");
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new ReflectionException(e, "Failed to instantiate the object");
+ }
+ catch (InstantiationException e)
+ {
+ throw new ReflectionException(e, "Failed to instantiate the object");
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new MBeanException((Exception) e.getCause(), "The constructor "
+ + name + " threw an exception");
+ }
+ }
+
+ /**
+ * <p>
+ * Creates an instance of the specified class using the supplied
+ * class loader. If the class loader given is <code>null</code>,
+ * then the class loader of the server will be used. The class
+ * should have a public constructor with no arguments. A reference
+ * to the new instance is returned, but the instance is not yet
+ * registered with the server.
+ * </p>
+ * <p>
+ * This method is equivalent to calling {@link
+ * #instantiate(String, ObjectName, Object[], String[])
+ * <code>instantiate(name, loaderName, (Object[]) null,
+ * (String[]) null)</code>} with <code>null</code> parameters
+ * and signature.
+ * </p>
+ *
+ * @param name the name of the class of bean to be instantiated.
+ * @param loaderName the name of the class loader to use.
+ * @return an instance of the given class.
+ * @throws InstanceNotFoundException if the class loader is not
+ * registered with the server.
+ * @throws ReflectionException if an exception is thrown during
+ * loading the class or calling the
+ * constructor.
+ * @throws MBeanException if the constructor throws an exception.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> name.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, null,
+ * "instantiate")</code>}.
+ * @see #instantiate(String, Object[], String[])
+ */
+ public Object instantiate(String name, ObjectName loaderName)
+ throws InstanceNotFoundException, ReflectionException,
+ MBeanException
+ {
+ return instantiate(name, loaderName);
+ }
+
+ /**
+ * Creates an instance of the specified class using the supplied
+ * class loader. If the class loader given is <code>null</code>,
+ * then the class loader of the server will be used. The class
+ * should have a public constructor matching the supplied
+ * signature. A reference to the new instance is returned,
+ * but the instance is not yet registered with the server.
+ *
+ * @param name the name of the class of bean to be instantiated.
+ * @param loaderName the name of the class loader to use.
+ * @param params the parameters for the constructor.
+ * @param sig the signature of the constructor.
+ * @return an instance of the given class.
+ * @throws InstanceNotFoundException if the class loader is not
+ * registered with the server.
+ * @throws ReflectionException if an exception is thrown during
+ * loading the class or calling the
+ * constructor.
+ * @throws MBeanException if the constructor throws an exception.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> name.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, null,
+ * "instantiate")</code>}.
+ */
+ public Object instantiate(String name, ObjectName loaderName,
+ Object[] params, String[] sig)
+ throws InstanceNotFoundException, ReflectionException,
+ MBeanException
+ {
+ checkSecurity(null, null, "instantiate");
+ if (name == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The name was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ ClassLoader loader = getClassLoader(loaderName);
+ Class[] sigTypes = new Class[sig.length];
+ for (int a = 0; a < sig.length; ++a)
+ {
+ try
+ {
+ sigTypes[a] = Class.forName(sig[a], true, loader);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new ReflectionException(e, "The class, " + sig[a] +
+ ", in the method signature " +
+ "could not be loaded.");
+ }
+ }
+ try
+ {
+ Constructor cons =
+ Class.forName(name, true, loader).getConstructor(sigTypes);
+ return cons.newInstance(params);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new ReflectionException(e, "The class, " + name +
+ ", of the constructor " +
+ "could not be loaded.");
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new ReflectionException(e, "The method, " + name +
+ ", could not be found.");
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new ReflectionException(e, "Failed to instantiate the object");
+ }
+ catch (InstantiationException e)
+ {
+ throw new ReflectionException(e, "Failed to instantiate the object");
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new MBeanException((Exception) e.getCause(), "The constructor "
+ + name + " threw an exception");
+ }
+ }
+
+ /**
+ * Invokes the supplied operation on the specified management
+ * bean. The class objects specified in the signature are loaded
+ * using the same class loader as was used for the management bean.
+ *
+ * @param bean the management bean whose operation should be invoked.
+ * @param name the name of the operation to invoke.
+ * @param params the parameters of the operation.
+ * @param sig the signature of the operation.
+ * @return the return value of the method.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws MBeanException if the method invoked throws an exception.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> name.
+ * @throws ReflectionException if an exception is thrown in invoking the
+ * method.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, name, bean,
+ * "invoke")</code>}.
+ * @see DynamicMBean#invoke(String, Object[], String[])
+ */
+ public Object invoke(ObjectName bean, String name, Object[] params, String[] sig)
+ throws InstanceNotFoundException, MBeanException,
+ ReflectionException
+ {
+ if (bean == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The bean was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ Object abean = getBean(bean);
+ checkSecurity(bean, name, "invoke");
+ if (abean instanceof DynamicMBean)
+ return ((DynamicMBean) abean).invoke(name, params, sig);
+ else
+ try
+ {
+ return new StandardMBean(abean, null).invoke(name, params, sig);
+ }
+ catch (NotCompliantMBeanException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to create dynamic bean.").initCause(e));
+ }
+ }
+
+ /**
+ * <p>
+ * Returns true if the specified management bean is an instance
+ * of the supplied class.
+ * </p>
+ * <p>
+ * A bean, B, is an instance of a class, C, if either of the following
+ * conditions holds:
+ * </p>
+ * <ul>
+ * <li>The class name in B's {@link MBeanInfo} is equal to the supplied
+ * name.</li>
+ * <li>Both the class of B and C were loaded by the same class loader,
+ * and B is assignable to C.</li>
+ * </ul>
+ *
+ * @param name the name of the management bean.
+ * @param className the name of the class to test if <code>name</code> is
+ * an instance of.
+ * @return true if either B is directly an instance of the named class,
+ * or B is assignable to the class, given that both it and B's
+ * current class were loaded using the same class loader.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "isInstanceOf")</code>
+ */
+ public boolean isInstanceOf(ObjectName name, String className)
+ throws InstanceNotFoundException
+ {
+ Object bean = getBean(name);
+ checkSecurity(name, null, "isInstanceOf");
+ MBeanInfo info;
+ if (bean instanceof DynamicMBean)
+ info = ((DynamicMBean) bean).getMBeanInfo();
+ else
+ try
+ {
+ info = new StandardMBean(bean, null).getMBeanInfo();
+ }
+ catch (NotCompliantMBeanException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to create dynamic bean.").initCause(e));
+ }
+ if (info.getClassName().equals(className))
+ return true;
+ Class bclass = bean.getClass();
+ try
+ {
+ Class oclass = Class.forName(className);
+ return (bclass.getClassLoader().equals(oclass.getClassLoader()) &&
+ oclass.isAssignableFrom(bclass));
+ }
+ catch (ClassNotFoundException e)
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Returns true if the specified management bean is registered with
+ * the server.
+ *
+ * @param name the name of the management bean.
+ * @return true if the bean is registered.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> bean name.
+ */
+ public boolean isRegistered(ObjectName name)
+ {
+ if (name == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The name was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ return beans.containsKey(name);
+ }
+
+ /**
+ * <p>
+ * Returns a set of {@link ObjectInstance}s matching the specified
+ * criteria. The full set of beans registered with the server
+ * are passed through two filters:
+ * </p>
+ * <ol>
+ * <li>Pattern matching is performed using the supplied
+ * {@link ObjectName}.</li>
+ * <li>The supplied query expression is applied.</li>
+ * </ol>
+ * <p>
+ * If both the object name and the query expression are <code>null</code>,
+ * or the object name has no domain and no key properties,
+ * no filtering will be performed and all beans are returned.
+ * </p>
+ *
+ * @param name an {@link ObjectName} to use as a filter.
+ * @param query a query expression to apply to each of the beans that match
+ * the given object name.
+ * @return a set of {@link ObjectInstance}s matching the filtered beans.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(null, null, name,
+ * "queryMBeans")</code>}. Additionally,
+ * for an bean, <code>b</code>, the
+ * caller's permission must imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, b, name,
+ * "queryMBeans")</code>} or that bean will
+ * not be included. Such an exception may also
+ * arise from the execution of the query, in which
+ * case that particular bean will again be excluded.
+ */
+ public Set queryMBeans(ObjectName name, QueryExp query)
+ {
+ checkSecurity(name, null, "queryMBeans");
+ Set results = new HashSet();
+ Iterator iterator = beans.entrySet().iterator();
+ while (iterator.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ ObjectName nextName = (ObjectName) entry.getKey();
+ checkSecurity(name, nextName.toString(), "queryMBeans");
+ try
+ {
+ if ((name == null || name.apply(nextName)) &&
+ (query == null || query.apply(nextName)))
+ results.add(((ServerInfo) entry.getValue()).getInstance());
+ }
+ catch (BadStringOperationException e)
+ {
+ /* Ignored -- assume false result */
+ }
+ catch (BadBinaryOpValueExpException e)
+ {
+ /* Ignored -- assume false result */
+ }
+ catch (BadAttributeValueExpException e)
+ {
+ /* Ignored -- assume false result */
+ }
+ catch (InvalidApplicationException e)
+ {
+ /* Ignored -- assume false result */
+ }
+ }
+ return results;
+ }
+
+ /**
+ * <p>
+ * Returns a set of {@link ObjectName}s matching the specified
+ * criteria. The full set of beans registered with the server
+ * are passed through two filters:
+ * </p>
+ * <ol>
+ * <li>Pattern matching is performed using the supplied
+ * {@link ObjectName}.</li>
+ * <li>The supplied query expression is applied.</li>
+ * </ol>
+ * <p>
+ * If both the object name and the query expression are <code>null</code>,
+ * or the object name has no domain and no key properties,
+ * no filtering will be performed and all beans are returned.
+ * </p>
+ *
+ * @param name an {@link ObjectName} to use as a filter.
+ * @param query a query expression to apply to each of the beans that match
+ * the given object name.
+ * @return a set of {@link ObjectName}s matching the filtered beans.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(null, null, name,
+ * "queryNames")</code>}. Additionally,
+ * for an name, <code>n</code>, the
+ * caller's permission must imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, n, name,
+ * "queryNames")</code>} or that name will
+ * not be included. Such an exception may also
+ * arise from the execution of the query, in which
+ * case that particular bean will again be excluded.
+ * Note that these permissions are implied if the
+ * <code>queryMBeans</code> permissions are available.
+ */
+ public Set queryNames(ObjectName name, QueryExp query)
+ {
+ checkSecurity(name, null, "queryNames");
+ Set results = new HashSet();
+ Iterator iterator = beans.entrySet().iterator();
+ while (iterator.hasNext())
+ {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ ObjectName nextName = (ObjectName) entry.getKey();
+ checkSecurity(name, nextName.toString(), "queryNames");
+ try
+ {
+ if ((name == null || name.apply(nextName)) &&
+ (query == null || query.apply(nextName)))
+ results.add(nextName);
+ }
+ catch (BadStringOperationException e)
+ {
+ /* Ignored -- assume false result */
+ }
+ catch (BadBinaryOpValueExpException e)
+ {
+ /* Ignored -- assume false result */
+ }
+ catch (BadAttributeValueExpException e)
+ {
+ /* Ignored -- assume false result */
+ }
+ catch (InvalidApplicationException e)
+ {
+ /* Ignored -- assume false result */
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Registers the supplied instance with the server, using the specified
+ * {@link ObjectName}. If the name given is <code>null</code>, then
+ * the bean supplied is expected to implement the {@link MBeanRegistration}
+ * interface and provide the name via the
+ * {@link MBeanRegistration#preRegister preRegister} method
+ * of this interface.
+ *
+ * @param obj the object to register with the server.
+ * @param name the name under which to register the object,
+ * or <code>null</code> if the {@link MBeanRegistration}
+ * interface should be used.
+ * @return an {@link ObjectInstance} containing the supplied
+ * {@link ObjectName} along with the name of the bean's class.
+ * @throws InstanceAlreadyExistsException if a matching instance
+ * already exists.
+ * @throws MBeanRegistrationException if an exception occurs in
+ * calling the preRegister
+ * method.
+ * @throws NotCompliantMBeanException if the created bean is not
+ * compliant with the JMX specification.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> object.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "registerMBean")</code>}. <code>className</code>
+ * here corresponds to the result of
+ * {@link MBeanInfo#getClassName()} for objects of
+ * this class. If this check succeeds, a check
+ * is also made on its
+ * {@link java.security.ProtectionDomain} to ensure
+ * it implies {@link MBeanTrustPermission(String)
+ * <code>MBeanTrustPermission("register")</code>}.
+ * The use of the {@link MBeanRegistration} interface
+ * results in another {@link MBeanPermission} check
+ * being made on the returned {@link ObjectName}.
+ */
+ public ObjectInstance registerMBean(Object obj, ObjectName name)
+ throws InstanceAlreadyExistsException, MBeanRegistrationException,
+ NotCompliantMBeanException
+ {
+ SecurityManager sm = System.getSecurityManager();
+ Class cl = obj.getClass();
+ String className = cl.getName();
+ if (sm != null)
+ {
+ sm.checkPermission(new MBeanPermission(className, null, name,
+ "registerMBean"));
+ if (!(cl.getProtectionDomain().implies(new MBeanTrustPermission("register"))))
+ throw new SecurityException("The protection domain of the object's class" +
+ "does not imply the trust permission," +
+ "register");
+ }
+ if (obj == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The object was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ MBeanRegistration register = null;
+ if (obj instanceof MBeanRegistration)
+ register = (MBeanRegistration) obj;
+ if (name == null)
+ {
+ if (register == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The name was null and " +
+ "the bean does not implement " +
+ "MBeanRegistration.");
+ throw new RuntimeOperationsException(e);
+ }
+ try
+ {
+ name = register.preRegister(this, null);
+ if (sm != null)
+ sm.checkPermission(new MBeanPermission(className, null, name,
+ "registerMBean"));
+ }
+ catch (SecurityException e)
+ {
+ register.postRegister(Boolean.FALSE);
+ throw e;
+ }
+ catch (Exception e)
+ {
+ register.postRegister(Boolean.FALSE);
+ throw new MBeanRegistrationException(e, "Pre-registration failed.");
+ }
+ }
+ if (beans == null)
+ beans = new HashMap();
+ else if (beans.containsKey(name))
+ {
+ if (register != null)
+ register.postRegister(Boolean.FALSE);
+ throw new InstanceAlreadyExistsException(name + "is already registered.");
+ }
+ ObjectInstance obji = new ObjectInstance(name, className);
+ beans.put(name, new ServerInfo(obji, obj));
+ if (register != null)
+ register.postRegister(Boolean.TRUE);
+ return obji;
+ }
+
+ /**
+ * Removes the specified listener from the list of recipients
+ * of notifications from the supplied bean. This includes all
+ * combinations of filters and passback objects registered for
+ * this listener. For more specific removal of listeners, see
+ * {@link #removeNotificationListener(ObjectName,
+ * NotificationListener,NotificationFilter,Object)}
+ *
+ * @param name the name of the management bean from which the
+ * listener should be removed.
+ * @param listener the listener to remove.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws ListenerNotFoundException if the specified listener
+ * is not registered with the bean.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "removeNotificationListener")</code>}.
+ * @see #addNotificationListener(NotificationListener, NotificationFilter,
+ * java.lang.Object)
+ * @see NotificationBroadcaster#removeNotificationListener(NotificationListener)
+ */
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener)
+ throws InstanceNotFoundException, ListenerNotFoundException
+ {
+ Object bean = getBean(name);
+ checkSecurity(name, null, "removeNotificationListener");
+ if (bean instanceof NotificationBroadcaster)
+ {
+ NotificationBroadcaster bbean = (NotificationBroadcaster) bean;
+ NotificationListener indirection = (NotificationListener)
+ listeners.get(listener);
+ if (indirection == null)
+ bbean.removeNotificationListener(listener);
+ else
+ {
+ bbean.removeNotificationListener(indirection);
+ listeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Removes the specified listener from the list of recipients
+ * of notifications from the supplied bean. Only the first instance with
+ * the supplied filter and passback object is removed.
+ * <code>null</code> is used as a valid value for these parameters,
+ * rather than as a way to remove all registration instances for
+ * the specified listener; for this behaviour instead, see
+ * {@link #removeNotificationListener(ObjectName, NotificationListener)}.
+ *
+ * @param name the name of the management bean from which the
+ * listener should be removed.
+ * @param listener the listener to remove.
+ * @param filter the filter of the listener to remove.
+ * @param passback the passback object of the listener to remove.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws ListenerNotFoundException if the specified listener
+ * is not registered with the bean.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "removeNotificationListener")</code>}.
+ * @see #addNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object)
+ * @see NotificationEmitter#removeNotificationListener(NotificationListener,
+ * NotificationFilter,
+ * Object)
+ */
+ public void removeNotificationListener(ObjectName name,
+ NotificationListener listener,
+ NotificationFilter filter,
+ Object passback)
+ throws InstanceNotFoundException, ListenerNotFoundException
+ {
+ Object bean = getBean(name);
+ checkSecurity(name, null, "removeNotificationListener");
+ if (bean instanceof NotificationEmitter)
+ {
+ NotificationEmitter bbean = (NotificationEmitter) bean;
+ NotificationListener indirection = (NotificationListener)
+ listeners.get(listener);
+ if (indirection == null)
+ bbean.removeNotificationListener(listener, filter, passback);
+ else
+ {
+ bbean.removeNotificationListener(indirection, filter, passback);
+ listeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Removes the specified listener from the list of recipients
+ * of notifications from the supplied bean. This includes all
+ * combinations of filters and passback objects registered for
+ * this listener. For more specific removal of listeners, see
+ * {@link #removeNotificationListener(ObjectName,
+ * ObjectName,NotificationFilter,Object)}
+ *
+ * @param name the name of the management bean from which the
+ * listener should be removed.
+ * @param listener the name of the listener to remove.
+ * @throws InstanceNotFoundException if a name doesn't match a registered
+ * bean.
+ * @throws ListenerNotFoundException if the specified listener
+ * is not registered with the bean.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "removeNotificationListener")</code>}.
+ * @see #addNotificationListener(NotificationListener, NotificationFilter,
+ * java.lang.Object)
+ * @see NotificationBroadcaster#removeNotificationListener(NotificationListener)
+ */
+ public void removeNotificationListener(ObjectName name, ObjectName listener)
+ throws InstanceNotFoundException, ListenerNotFoundException
+ {
+ Object lbean = getBean(listener);
+ if (!(lbean instanceof NotificationListener))
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The supplied listener name does not " +
+ "correspond to a notification listener.");
+ throw new RuntimeOperationsException(e);
+ }
+ removeNotificationListener(name, ((NotificationListener) lbean));
+ }
+
+ /**
+ * Removes the specified listener from the list of recipients
+ * of notifications from the supplied bean. Only the first instance with
+ * the supplied filter and passback object is removed.
+ * <code>null</code> is used as a valid value for these parameters,
+ * rather than as a way to remove all registration instances for
+ * the specified listener; for this behaviour instead, see
+ * {@link #removeNotificationListener(ObjectName, ObjectName)}.
+ *
+ * @param name the name of the management bean from which the
+ * listener should be removed.
+ * @param listener the name of the listener to remove.
+ * @param filter the filter of the listener to remove.
+ * @param passback the passback object of the listener to remove.
+ * @throws InstanceNotFoundException if a name doesn't match a registered
+ * bean.
+ * @throws ListenerNotFoundException if the specified listener
+ * is not registered with the bean.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "removeNotificationListener")</code>}.
+ * @see #addNotificationListener(ObjectName, NotificationListener,
+ * NotificationFilter, Object)
+ * @see NotificationEmitter#removeNotificationListener(NotificationListener,
+ * NotificationFilter,
+ * Object)
+ */
+ public void removeNotificationListener(ObjectName name,
+ ObjectName listener,
+ NotificationFilter filter,
+ Object passback)
+ throws InstanceNotFoundException, ListenerNotFoundException
+ {
+ Object lbean = getBean(listener);
+ if (!(lbean instanceof NotificationListener))
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The supplied listener name does not " +
+ "correspond to a notification listener.");
+ throw new RuntimeOperationsException(e);
+ }
+ removeNotificationListener(name, ((NotificationListener) lbean), filter,
+ passback);
+ }
+
+ /**
+ * Sets the value of the specified attribute of the supplied
+ * management bean.
+ *
+ * @param name the name of the management bean.
+ * @param attribute the attribute to set.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws AttributeNotFoundException if the attribute does not
+ * correspond to an attribute
+ * of the bean.
+ * @throws InvalidAttributeValueException if the value is invalid
+ * for this particular
+ * attribute of the bean.
+ * @throws MBeanException if setting the attribute causes
+ * the bean to throw an exception (which
+ * becomes the cause of this exception).
+ * @throws ReflectionException if an exception occurred in trying
+ * to use the reflection interface
+ * to lookup the attribute. The
+ * thrown exception is the cause of
+ * this exception.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> bean or attribute
+ * name.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, name, bean,
+ * "setAttribute")</code>}.
+ * @see #getAttribute(ObjectName, String)
+ * @see DynamicMBean#setAttribute(Attribute)
+ */
+ public void setAttribute(ObjectName name, Attribute attribute)
+ throws InstanceNotFoundException, AttributeNotFoundException,
+ InvalidAttributeValueException, MBeanException,
+ ReflectionException
+ {
+ if (attribute == null || name == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("One of the supplied arguments was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ Object bean = getBean(name);
+ checkSecurity(name, attribute.getName(), "setAttribute");
+ if (bean instanceof DynamicMBean)
+ ((DynamicMBean) bean).setAttribute(attribute);
+ else
+ try
+ {
+ new StandardMBean(bean, null).setAttribute(attribute);
+ }
+ catch (NotCompliantMBeanException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to create dynamic bean.").initCause(e));
+ }
+ }
+
+ /**
+ * Sets the value of each of the specified attributes
+ * of the supplied management bean to that specified by
+ * the {@link Attribute} object. The returned list contains
+ * the attributes that were set and their new values.
+ *
+ * @param name the name of the management bean.
+ * @param attributes the attributes to set.
+ * @return a list of the changed attributes.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws ReflectionException if an exception occurred in trying
+ * to use the reflection interface
+ * to lookup the attribute. The
+ * thrown exception is the cause of
+ * this exception.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> bean or attribute
+ * list.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, bean,
+ * "setAttribute")</code>}. Additionally,
+ * for an attribute name, <code>n</code>, the
+ * caller's permission must imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, n, bean,
+ * "setAttribute")</code>} or that attribute will
+ * not be included.
+ * @see #getAttributes(ObjectName, String[])
+ * @see DynamicMBean#setAttributes(AttributeList)
+ */
+ public AttributeList setAttributes(ObjectName name, AttributeList attributes)
+ throws InstanceNotFoundException, ReflectionException
+ {
+ if (name == null || attributes == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("One of the supplied arguments was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ Object abean = getBean(name);
+ checkSecurity(name, null, "setAttribute");
+ AttributeList list = new AttributeList(attributes.size());
+ Iterator it = attributes.iterator();
+ while (it.hasNext())
+ {
+ try
+ {
+ Attribute attrib = (Attribute) it.next();
+ if (attrib == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("An attribute was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ checkSecurity(name, attrib.getName(), "setAttribute");
+ if (abean instanceof DynamicMBean)
+ ((DynamicMBean) abean).setAttribute(attrib);
+ else
+ try
+ {
+ new StandardMBean(abean, null).setAttribute(attrib);
+ }
+ catch (NotCompliantMBeanException e)
+ {
+ throw (Error)
+ (new InternalError("Failed to create dynamic bean.").initCause(e));
+ }
+ list.add(attrib);
+ }
+ catch (AttributeNotFoundException e)
+ {
+ /* Ignored */
+ }
+ catch (InvalidAttributeValueException e)
+ {
+ /* Ignored */
+ }
+ catch (MBeanException e)
+ {
+ /* Ignored */
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Unregisters the specified management bean. Following this operation,
+ * the bean instance is no longer accessible from the server via this
+ * name. Prior to unregistering the bean, the
+ * {@link MBeanRegistration#preDeregister()} method will be called if
+ * the bean implements the {@link MBeanRegistration} interface.
+ *
+ * @param name the name of the management bean.
+ * @throws InstanceNotFoundException if the bean can not be found.
+ * @throws MBeanRegistrationException if an exception occurs in
+ * calling the preDeregister
+ * method.
+ * @throws RuntimeOperationsException if an {@link IllegalArgumentException}
+ * is thrown by the server due to a
+ * <code>null</code> bean name or a
+ * request being made to unregister the
+ * {@link MBeanServerDelegate} bean.
+ * @throws SecurityException if a security manager exists and the
+ * caller's permissions don't imply {@link
+ * MBeanPermission(String,String,ObjectName,String)
+ * <code>MBeanPermission(className, null, name,
+ * "unregisterMBean")</code>}.
+ */
+ public void unregisterMBean(ObjectName name)
+ throws InstanceNotFoundException, MBeanRegistrationException
+ {
+ if (name == null)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The name was null.");
+ throw new RuntimeOperationsException(e);
+ }
+ Object bean = getBean(name);
+ checkSecurity(name, null, "unregisterMBean");
+ if (bean == delegate)
+ {
+ RuntimeException e =
+ new IllegalArgumentException("The delegate can not be unregistered.");
+ throw new RuntimeOperationsException(e);
+ }
+ MBeanRegistration register = null;
+ if (bean instanceof MBeanRegistration)
+ {
+ register = (MBeanRegistration) bean;
+ try
+ {
+ register.preDeregister();
+ }
+ catch (Exception e)
+ {
+ throw new MBeanRegistrationException(e, "Pre-deregistration failed.");
+ }
+ }
+ beans.remove(name);
+ if (register != null)
+ register.postDeregister();
+ }
+
+ /**
+ * Input stream which deserializes using the given classloader.
+ */
+ private class ServerInputStream
+ extends ObjectInputStream
+ {
+
+ private ClassLoader cl;
+
+ public ServerInputStream(InputStream is, ClassLoader cl)
+ throws IOException, StreamCorruptedException
+ {
+ super(is);
+ this.cl = cl;
+ }
+
+ protected Class resolveClass(ObjectStreamClass osc)
+ throws ClassNotFoundException, IOException
+ {
+ try
+ {
+ return Class.forName(osc.getName(), true, cl);
+ }
+ catch (ClassNotFoundException e)
+ {
+ return super.resolveClass(osc);
+ }
+ }
+
+ }
+
+ /**
+ * Holder for information on registered beans.
+ */
+ private class ServerInfo
+ {
+ private ObjectInstance instance;
+
+ private Object object;
+
+ public ServerInfo(ObjectInstance instance, Object object)
+ {
+ this.instance = instance;
+ this.object = object;
+ }
+
+ public Object getObject()
+ {
+ return object;
+ }
+
+ public ObjectInstance getInstance()
+ {
+ return instance;
+ }
+ }
+
+ /**
+ * Notification listener which removes direct references
+ * to beans.
+ */
+ private class ServerNotificationListener
+ implements NotificationListener
+ {
+
+ /**
+ * The bean from which notifications are emitted.
+ */
+ Object bean;
+
+ /**
+ * The {@link ObjectName} of the emitting bean.
+ */
+ ObjectName name;
+
+ /**
+ * The real {@link NotificationListener}.
+ */
+ NotificationListener listener;
+
+ /**
+ * Constructs a new {@link ServerNotificationListener} replacing
+ * occurrences of <code>bean</code> with its name.
+ *
+ * @param bean the bean emitting notifications.
+ * @param name the object name of the emitting bean.
+ * @param listener the listener events eventually reach.
+ */
+ public ServerNotificationListener(Object bean, ObjectName name,
+ NotificationListener listener)
+ {
+ this.bean = bean;
+ this.name = name;
+ this.listener = listener;
+ }
+
+ /**
+ * Replace a direct reference to <code>bean</code> with its
+ * object reference, if necessary, before calling the listener.
+ *
+ * @param notif the notification being emitted.
+ * @param handback an object that will be returned to the notification
+ * listener when an event occurs.
+ */
+ public void handleNotification(Notification notif, Object handback)
+ {
+ if (notif.getSource() == bean)
+ notif.setSource(name);
+ listener.handleNotification(notif, handback);
+ }
+
+ }
+
+}
diff --git a/gnu/javax/swing/text/html/css/CSSColor.java b/gnu/javax/swing/text/html/css/CSSColor.java
index 57230f12a..ea4b94ae0 100644
--- a/gnu/javax/swing/text/html/css/CSSColor.java
+++ b/gnu/javax/swing/text/html/css/CSSColor.java
@@ -110,9 +110,16 @@ public class CSSColor
val1 = (String) COLOR_MAP.get(val1);
if (val1 != null)
{
- String hexVal = val1.substring(1);
- int rgb = Integer.parseInt(hexVal, 16);
- color = new Color(rgb);
+ String hexVal = val1.substring(1).trim();
+ try
+ {
+ int rgb = Integer.parseInt(hexVal, 16);
+ color = new Color(rgb);
+ }
+ catch (NumberFormatException ex)
+ {
+ color = Color.BLACK;
+ }
}
else
color = null;
diff --git a/gnu/javax/swing/text/html/css/CSSParser.java b/gnu/javax/swing/text/html/css/CSSParser.java
index 0d68457a3..5647e2506 100644
--- a/gnu/javax/swing/text/html/css/CSSParser.java
+++ b/gnu/javax/swing/text/html/css/CSSParser.java
@@ -45,6 +45,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
+import java.util.StringTokenizer;
/**
* A parser for CSS stylesheets.
@@ -158,7 +159,15 @@ public class CSSParser
{
StringBuilder selector = new StringBuilder();
parseSelector(selector);
- callback.startStatement(new Selector(selector.toString()));
+ StringTokenizer selSplitter =
+ new StringTokenizer(selector.toString(), ",");
+ Selector[] sels = new Selector[selSplitter.countTokens()];
+ for (int i = 0; selSplitter.hasMoreTokens(); i++)
+ {
+ String sel = selSplitter.nextToken().trim();
+ sels[i] = new Selector(sel);
+ }
+ callback.startStatement(sels);
// Read any number of whitespace.
int token;
do
@@ -260,7 +269,7 @@ public class CSSParser
StringBuilder value = new StringBuilder();
if (parseValue(value))
{
- callback.declaration(property, value.toString());
+ callback.declaration(property, value.toString().trim());
}
else
{
@@ -458,9 +467,17 @@ public class CSSParser
InputStreamReader r = new InputStreamReader(bin);
CSSParserCallback cb = new CSSParserCallback()
{
- public void startStatement(Selector selector)
+ public void startStatement(Selector[] selector)
{
- System.out.println("startStatement: " + selector);
+ System.out.print("startStatement: ");
+ for (int i = 0; i < selector.length; i++)
+ {
+ System.out.print(selector[i]);
+ if (i < selector.length - 1)
+ System.out.print(',');
+ else
+ System.out.println();
+ }
}
public void endStatement()
{
diff --git a/gnu/javax/swing/text/html/css/CSSParserCallback.java b/gnu/javax/swing/text/html/css/CSSParserCallback.java
index b62baddfc..f49ffa232 100644
--- a/gnu/javax/swing/text/html/css/CSSParserCallback.java
+++ b/gnu/javax/swing/text/html/css/CSSParserCallback.java
@@ -62,7 +62,7 @@ public interface CSSParserCallback
*
* @param selector the selector of the statement.
*/
- void startStatement(Selector selector);
+ void startStatement(Selector[] selector);
/**
* Signals the end of a statement.
diff --git a/gnu/javax/swing/text/html/css/FontSize.java b/gnu/javax/swing/text/html/css/FontSize.java
index 19e1d701a..7dc8d46c6 100644
--- a/gnu/javax/swing/text/html/css/FontSize.java
+++ b/gnu/javax/swing/text/html/css/FontSize.java
@@ -52,6 +52,24 @@ public class FontSize
private String value;
/**
+ * The actual font size.
+ */
+ private int size;
+
+ /**
+ * The index of one of the standard sizes that this font size maps to.
+ * This is -1 if this fontsize doesn't map to one of the standard sizes.
+ *
+ * @see #SCALE
+ */
+ private int sizeIndex;
+
+ /**
+ * True when this font size is relative.
+ */
+ private boolean isRelative;
+
+ /**
* The default size for 'medium' absolute size. The other absolute sizes
* are calculated from this.
*/
@@ -70,6 +88,27 @@ public class FontSize
public FontSize(String val)
{
value = val;
+ sizeIndex = -1;
+ isRelative = false;
+ size = mapValue();
+ }
+
+ /**
+ * Returns the font size value.
+ *
+ * @return the font size value
+ */
+ public int getValue(int p)
+ {
+ if (isRelative)
+ mapRelative(p);
+ return size;
+ }
+
+ public int getValue()
+ {
+ assert ! isRelative;
+ return size;
}
/**
@@ -77,17 +116,21 @@ public class FontSize
*
* @return the converted real value in point
*/
- public int getValue()
+ private int mapValue()
{
int intVal;
if (value.contains("pt"))
intVal = mapPoints();
else if (value.contains("px"))
intVal = mapPixels();
+ else if (value.contains("em") || value.contains("%")
+ || value.contains("larger") || value.contains("smaller"))
+ {
+ intVal = -1;
+ isRelative = true;
+ }
else
intVal = mapAbsolute();
- // FIXME: Allow relative font values, ('larger' and 'smaller'). This
- // requires knowledge about the parent element's font size.
return intVal;
}
@@ -111,10 +154,65 @@ public class FontSize
*/
private int mapPixels()
{
- int end = value.indexOf("pt");
+ int end = value.indexOf("px");
+ if (end == -1)
+ end = value.length();
String number = value.substring(0, end);
- int intVal = Integer.parseInt(number);
- return intVal;
+ try
+ {
+ int intVal = Integer.parseInt(number);
+ return intVal;
+ }
+ catch (NumberFormatException ex)
+ {
+ return DEFAULT_FONT_SIZE;
+ }
+ }
+
+ private int mapPercent(int par)
+ {
+ int end = value.indexOf("%");
+ if (end == -1)
+ end = value.length();
+ String number = value.substring(0, end);
+ try
+ {
+ int intVal = Integer.parseInt(number);
+ return intVal * par / 100;
+ }
+ catch (NumberFormatException ex)
+ {
+ System.err.println("couldn't map value: '" + value + "'");
+ return DEFAULT_FONT_SIZE;
+ }
+ }
+
+ private int mapEM(int par)
+ {
+ int end = value.indexOf("em");
+ if (end == -1)
+ end = value.length();
+ String number = value.substring(0, end);
+ try
+ {
+ float factor = Float.parseFloat(number);
+ // FIXME: Should be relative to the parent element's size.
+ return (int) (factor * par);
+ }
+ catch (NumberFormatException ex)
+ {
+ return DEFAULT_FONT_SIZE;
+ }
+ }
+
+ private int mapSmaller(int par)
+ {
+ return (int) (par * 0.9);
+ }
+
+ private int mapLarger(int par)
+ {
+ return (int) (par * 0.9);
}
/**
@@ -143,6 +241,7 @@ public class FontSize
// FIXME: Scale the real medium size of the document, rather than the
// constant here.
int intVal = (int) (scale * DEFAULT_FONT_SIZE);
+ sizeIndex = index;
return intVal;
}
@@ -153,4 +252,22 @@ public class FontSize
{
return value;
}
+
+ private int mapRelative(int par)
+ {
+ if (value.contains("%"))
+ size = mapPercent(par);
+ else if (value.contains("em"))
+ size = mapEM(par);
+ else if (value.contains("larger"))
+ size = mapLarger(par);
+ else if (value.contains("smaller"))
+ size = mapSmaller(par);
+ return size;
+ }
+
+ public boolean isRelative()
+ {
+ return isRelative;
+ }
}
diff --git a/gnu/javax/swing/text/html/css/Selector.java b/gnu/javax/swing/text/html/css/Selector.java
index 75f2d46c6..b8128233b 100644
--- a/gnu/javax/swing/text/html/css/Selector.java
+++ b/gnu/javax/swing/text/html/css/Selector.java
@@ -214,7 +214,7 @@ public class Selector
{
b++;
id = sel.substring(idIndex + 1,
- clazzIndex > 0 ? Math.min(idIndex, sel.length())
+ clazzIndex > 0 ? Math.min(clazzIndex, sel.length())
: sel.length());
}
String tag = sel.substring(0,
diff --git a/gnu/javax/swing/text/html/parser/htmlValidator.java b/gnu/javax/swing/text/html/parser/htmlValidator.java
index 4d287a677..7507850e8 100644
--- a/gnu/javax/swing/text/html/parser/htmlValidator.java
+++ b/gnu/javax/swing/text/html/parser/htmlValidator.java
@@ -153,7 +153,7 @@ public abstract class htmlValidator
* Remove the given tag from the stack or (if found) from the list
* of the forcibly closed tags.
*/
- public void closeTag(TagElement tElement)
+ public boolean closeTag(TagElement tElement)
{
HTML.Tag tag = tElement.getHTMLTag();
hTag x;
@@ -191,11 +191,12 @@ public abstract class htmlValidator
}
stack.remove(x);
- return;
+ return true;
}
}
}
s_error("Closing unopened <" + tag + ">");
+ return false;
}
/**
diff --git a/gnu/javax/swing/text/html/parser/support/Parser.java b/gnu/javax/swing/text/html/parser/support/Parser.java
index f1f25fad0..98058e503 100644
--- a/gnu/javax/swing/text/html/parser/support/Parser.java
+++ b/gnu/javax/swing/text/html/parser/support/Parser.java
@@ -659,19 +659,18 @@ public class Parser
else
text = textProcessor.preprocess(buffer);
- if (text != null && text.length > 0)
+ if (text != null && text.length > 0
+ // According to the specs we need to discard whitespace immediately
+ // before a closing tag.
+ && (text.length > 1 || text[0] != ' ' || ! TAG_CLOSE.matches(this)))
{
TagElement pcdata = new TagElement(dtd.getElement("#pcdata"));
- if ((text.length > 1 && text[0] != ' ')
- || validator.tagIsValidForContext(pcdata) == Boolean.TRUE)
- {
- attributes = htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET;
- _handleEmptyTag(pcdata);
+ attributes = htmlAttributeSet.EMPTY_HTML_ATTRIBUTE_SET;
+ _handleEmptyTag(pcdata);
- handleText(text);
- if (titleOpen)
- title.append(text);
- }
+ handleText(text);
+ if (titleOpen)
+ title.append(text);
}
}
@@ -1197,8 +1196,8 @@ public class Parser
*/
private void _handleEndTag(TagElement tag)
{
- validator.closeTag(tag);
- _handleEndTag_remaining(tag);
+ if (validator.closeTag(tag))
+ _handleEndTag_remaining(tag);
}
/**
@@ -1218,6 +1217,11 @@ public class Parser
if (preformatted < 0)
preformatted = 0;
+ // When a block tag is closed, consume whitespace that follows after
+ // it.
+ if (h.isBlock())
+ optional(WS);
+
if (h == HTML.Tag.TITLE)
{
titleOpen = false;
@@ -1460,7 +1464,12 @@ public class Parser
if (te.getElement().type == DTDConstants.EMPTY)
_handleEmptyTag(te);
else
- _handleStartTag(te);
+ {
+ // According to the specs we need to consume whitespace following
+ // immediately after a opening tag.
+ optional(WS);
+ _handleStartTag(te);
+ }
}
}
diff --git a/gnu/javax/swing/text/html/parser/support/low/Constants.java b/gnu/javax/swing/text/html/parser/support/low/Constants.java
index 283d32385..5416582ad 100644
--- a/gnu/javax/swing/text/html/parser/support/low/Constants.java
+++ b/gnu/javax/swing/text/html/parser/support/low/Constants.java
@@ -209,6 +209,17 @@ public class Constants
}
);
+ /**
+ * Ordinary HTML tag closing pattern.
+ */
+ public static final pattern TAG_CLOSE =
+ new pattern(new node[]
+ {
+ new node(BEGIN), new node(WS, true), new node(SLASH),
+ new node(WS, true), new node(NUMTOKEN)
+ }
+ );
+
/* Special tokens */
/**
diff --git a/gnu/javax/swing/text/html/parser/support/textPreProcessor.java b/gnu/javax/swing/text/html/parser/support/textPreProcessor.java
index b81275b1f..6fd79e258 100644
--- a/gnu/javax/swing/text/html/parser/support/textPreProcessor.java
+++ b/gnu/javax/swing/text/html/parser/support/textPreProcessor.java
@@ -65,22 +65,14 @@ public class textPreProcessor
int b = text.length - 1;
// Remove leading/trailing whitespace, leaving at most one character
- try
- {
- while (Constants.bWHITESPACE.get(text[a])
- && Constants.bWHITESPACE.get(text[a + 1]))
- a++;
+ int len = text.length;
+ while (a + 1 < len && Constants.bWHITESPACE.get(text[a])
+ && Constants.bWHITESPACE.get(text[a + 1]))
+ a++;
- while (b > a && Constants.bWHITESPACE.get(text[b])
+ while (b > a && Constants.bWHITESPACE.get(text[b])
&& Constants.bWHITESPACE.get(text[b - 1]))
- b--;
- }
- catch (ArrayIndexOutOfBoundsException sx)
- {
- // A text fragment, consisting from spaces and line breaks only,
- // mutates into single space.
- return new char[] { ' ' };
- }
+ b--;
a_text.setLength(0);
diff --git a/include/gnu_java_awt_peer_gtk_CairoGraphics2D.h b/include/gnu_java_awt_peer_gtk_CairoGraphics2D.h
index f32b9381b..c4a7f1b02 100644
--- a/include/gnu_java_awt_peer_gtk_CairoGraphics2D.h
+++ b/include/gnu_java_awt_peer_gtk_CairoGraphics2D.h
@@ -14,7 +14,7 @@ JNIEXPORT jlong JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_init (JNIEnv
JNIEXPORT void JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_disposeNative (JNIEnv *env, jobject, jlong);
JNIEXPORT void JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_drawPixels (JNIEnv *env, jobject, jlong, jintArray, jint, jint, jint, jdoubleArray, jdouble, jint);
JNIEXPORT void JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_setGradient (JNIEnv *env, jobject, jlong, jdouble, jdouble, jdouble, jdouble, jint, jint, jint, jint, jint, jint, jint, jint, jboolean);
-JNIEXPORT void JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_setPaintPixels (JNIEnv *env, jobject, jlong, jintArray, jint, jint, jint, jboolean);
+JNIEXPORT void JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_setPaintPixels (JNIEnv *env, jobject, jlong, jintArray, jint, jint, jint, jboolean, jint, jint);
JNIEXPORT void JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoSetMatrix (JNIEnv *env, jobject, jlong, jdoubleArray);
JNIEXPORT void JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoScale (JNIEnv *env, jobject, jlong, jdouble, jdouble);
JNIEXPORT void JNICALL Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoSetOperator (JNIEnv *env, jobject, jlong, jint);
diff --git a/java/awt/Component.java b/java/awt/Component.java
index 82c81c7a0..4b2c4496a 100644
--- a/java/awt/Component.java
+++ b/java/awt/Component.java
@@ -823,10 +823,7 @@ public abstract class Component
*/
public boolean isShowing()
{
- if (! visible || peer == null)
- return false;
-
- return parent == null ? false : parent.isShowing();
+ return visible && peer != null && (parent == null || parent.isShowing());
}
/**
@@ -3627,6 +3624,12 @@ public abstract class Component
else if (r2.contains(r1))
coalesced = newEvent;
}
+ else
+ {
+ // Replace the event and let the heavyweight figure out the expanding
+ // of the repaint area.
+ coalesced = newEvent;
+ }
break;
default:
coalesced = null;
diff --git a/java/awt/EventQueue.java b/java/awt/EventQueue.java
index 0b6e03efa..74dbd5fb6 100644
--- a/java/awt/EventQueue.java
+++ b/java/awt/EventQueue.java
@@ -584,8 +584,11 @@ public class EventQueue
prev = null;
// Tell our EventDispatchThread that it can end
// execution.
- dispatchThread.interrupt();
- dispatchThread = null;
+ if (dispatchThread != null)
+ {
+ dispatchThread.interrupt();
+ dispatchThread = null;
+ }
}
}
}
diff --git a/java/awt/Toolkit.java b/java/awt/Toolkit.java
index 5ac9e2766..69040722e 100644
--- a/java/awt/Toolkit.java
+++ b/java/awt/Toolkit.java
@@ -556,13 +556,6 @@ public abstract class Toolkit
if (toolkit != null)
return toolkit;
- // Check for the headless property.
- if (GraphicsEnvironment.isHeadless())
- {
- toolkit = new HeadlessToolkit();
- return toolkit;
- }
-
String toolkit_name = SystemProperties.getProperty("awt.toolkit",
default_toolkit_name);
try
@@ -592,8 +585,18 @@ public abstract class Toolkit
}
catch (Throwable t)
{
- AWTError e = new AWTError("Cannot load AWT toolkit: " + toolkit_name);
- throw (AWTError) e.initCause(t);
+ // Check for the headless property.
+ if (GraphicsEnvironment.isHeadless())
+ {
+ toolkit = new HeadlessToolkit();
+ return toolkit;
+ }
+ else
+ {
+ AWTError e = new AWTError("Cannot load AWT toolkit: "
+ + toolkit_name);
+ throw (AWTError) e.initCause(t);
+ }
}
}
diff --git a/java/awt/dnd/DropTarget.java b/java/awt/dnd/DropTarget.java
index 1e7b2c4cd..63be5ac04 100644
--- a/java/awt/dnd/DropTarget.java
+++ b/java/awt/dnd/DropTarget.java
@@ -38,12 +38,12 @@ exception statement from your version. */
package java.awt.dnd;
-import gnu.classpath.NotImplementedException;
-
import java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
+import java.awt.Insets;
import java.awt.Point;
+import java.awt.Rectangle;
import java.awt.datatransfer.FlavorMap;
import java.awt.datatransfer.SystemFlavorMap;
import java.awt.dnd.peer.DropTargetPeer;
@@ -55,6 +55,8 @@ import java.io.Serializable;
import java.util.EventListener;
import java.util.TooManyListenersException;
+import javax.swing.Timer;
+
/**
* @author Michael Koch
* @since 1.2
@@ -70,30 +72,87 @@ public class DropTarget
protected static class DropTargetAutoScroller
implements ActionListener
{
+ /**
+ * The threshold that keeps the autoscroller running.
+ */
+ private static final int HYSTERESIS = 10;
+
+ /**
+ * The initial timer delay.
+ */
+ private static final int DELAY = 100;
+
private Component component;
private Point point;
-
+
+ /**
+ * The timer that triggers autoscrolling.
+ */
+ private Timer timer;
+
+ /**
+ * The outer region of the scroller. This is the component's size.
+ */
+ private Rectangle outer;
+
+ /**
+ * The inner region of the scroller. This is the component size without
+ * the autoscroll insets.
+ */
+ private Rectangle inner;
+
protected DropTargetAutoScroller (Component c, Point p)
{
component = c;
point = p;
+ timer = new Timer(DELAY, this);
+ timer.setCoalesce(true);
+ timer.start();
}
protected void updateLocation (Point newLocn)
{
+ Point previous = point;
point = newLocn;
+ if (Math.abs(point.x - previous.x) > HYSTERESIS
+ || Math.abs(point.y - previous.y) > HYSTERESIS)
+ {
+ if (timer.isRunning())
+ timer.stop();
+ }
+ else
+ {
+ if (! timer.isRunning())
+ timer.start();
+ }
}
protected void stop ()
- throws NotImplementedException
{
- // FIXME: implement this
+ timer.start();
}
public void actionPerformed (ActionEvent e)
- throws NotImplementedException
{
- // FIXME: implement this
+ Autoscroll autoScroll = (Autoscroll) component;
+
+ // First synchronize the inner and outer rectangles.
+ Insets i = autoScroll.getAutoscrollInsets();
+ int width = component.getWidth();
+ int height = component.getHeight();
+ if (width != outer.width || height != outer.height)
+ outer.setBounds(0, 0, width, height);
+ if (inner.x != i.left || inner.y != i.top)
+ inner.setLocation(i.left, i.top);
+ int inWidth = width - i.left - i.right;
+ int inHeight = height - i.top - i.bottom;
+ if (inWidth != inner.width || inHeight != inner.height)
+ inner.setSize(inWidth, inHeight);
+
+ // Scroll if the outer rectangle contains the location, but the
+ // inner doesn't.
+ if (outer.contains(point) && ! inner.contains(point))
+ autoScroll.autoscroll(point);
}
}
@@ -182,6 +241,8 @@ public class DropTarget
*/
public void setComponent (Component c)
{
+ if (component != null)
+ clearAutoscroll();
component = c;
}
@@ -212,6 +273,8 @@ public class DropTarget
public void setActive (boolean active)
{
this.active = active;
+ if (! active)
+ clearAutoscroll();
}
public boolean isActive()
@@ -250,30 +313,47 @@ public class DropTarget
public void dragEnter(DropTargetDragEvent dtde)
{
- if (dropTargetListener != null)
- dropTargetListener.dragEnter(dtde);
+ if (active)
+ {
+ if (dropTargetListener != null)
+ dropTargetListener.dragEnter(dtde);
+ initializeAutoscrolling(dtde.getLocation());
+ }
}
public void dragOver(DropTargetDragEvent dtde)
{
- if (dropTargetListener != null)
- dropTargetListener.dragOver(dtde);
+ if (active)
+ {
+ if (dropTargetListener != null)
+ dropTargetListener.dragOver(dtde);
+ updateAutoscroll(dtde.getLocation());
+ }
}
public void dropActionChanged(DropTargetDragEvent dtde)
{
- if (dropTargetListener != null)
- dropTargetListener.dropActionChanged(dtde);
+ if (active)
+ {
+ if (dropTargetListener != null)
+ dropTargetListener.dropActionChanged(dtde);
+ updateAutoscroll(dtde.getLocation());
+ }
}
public void dragExit(DropTargetEvent dte)
{
- if (dropTargetListener != null)
- dropTargetListener.dragExit(dte);
+ if (active)
+ {
+ if (dropTargetListener != null)
+ dropTargetListener.dragExit(dte);
+ clearAutoscroll();
+ }
}
public void drop(DropTargetDropEvent dtde)
{
+ clearAutoscroll();
if (dropTargetListener != null)
dropTargetListener.drop(dtde);
}
@@ -332,15 +412,13 @@ public class DropTarget
protected DropTarget.DropTargetAutoScroller createDropTargetAutoScroller
(Component c, Point p)
{
- if (autoscroller == null)
- autoscroller = new DropTarget.DropTargetAutoScroller (c, p);
-
- return autoscroller;
+ return new DropTarget.DropTargetAutoScroller (c, p);
}
protected void initializeAutoscrolling(Point p)
{
- createDropTargetAutoScroller (component, p);
+ if (component instanceof Autoscroll) // Checks for null too.
+ autoscroller = createDropTargetAutoScroller (component, p);
}
protected void updateAutoscroll(Point dragCursorLocn)
@@ -351,6 +429,10 @@ public class DropTarget
protected void clearAutoscroll()
{
- autoscroller = null;
+ if (autoscroller != null)
+ {
+ autoscroller.stop();
+ autoscroller = null;
+ }
}
} // class DropTarget
diff --git a/java/awt/font/TextHitInfo.java b/java/awt/font/TextHitInfo.java
index 2b23e1963..f6fee1add 100644
--- a/java/awt/font/TextHitInfo.java
+++ b/java/awt/font/TextHitInfo.java
@@ -81,6 +81,9 @@ public final class TextHitInfo
public boolean equals(TextHitInfo hitInfo)
{
+ if (hitInfo == null)
+ return false;
+
return (charIndex == hitInfo.getCharIndex ())
&& (leadingEdge == hitInfo.isLeadingEdge ());
}
@@ -97,7 +100,7 @@ public final class TextHitInfo
public static TextHitInfo beforeOffset(int offset)
{
- return new TextHitInfo (offset, false);
+ return new TextHitInfo ((offset - 1), false);
}
public static TextHitInfo afterOffset(int offset)
diff --git a/java/awt/font/TextLayout.java b/java/awt/font/TextLayout.java
index 0fe4c0f73..dde28df0a 100644
--- a/java/awt/font/TextLayout.java
+++ b/java/awt/font/TextLayout.java
@@ -38,18 +38,18 @@ exception statement from your version. */
package java.awt.font;
-import gnu.classpath.NotImplementedException;
-
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
+import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.text.CharacterIterator;
import java.text.AttributedCharacterIterator;
import java.text.Bidi;
+import java.util.ArrayList;
import java.util.Map;
/**
@@ -57,19 +57,86 @@ import java.util.Map;
*/
public final class TextLayout implements Cloneable
{
- private GlyphVector[] runs;
- private Font font;
+ /**
+ * Holds the layout data that belongs to one run of characters.
+ */
+ private class Run
+ {
+ /**
+ * The actual glyph vector.
+ */
+ GlyphVector glyphVector;
+
+ /**
+ * The font for this text run.
+ */
+ Font font;
+
+ /**
+ * The start of the run.
+ */
+ int runStart;
+
+ /**
+ * The end of the run.
+ */
+ int runEnd;
+
+ /**
+ * The layout location of the beginning of the run.
+ */
+ float location;
+
+ /**
+ * Initializes the Run instance.
+ *
+ * @param gv the glyph vector
+ * @param start the start index of the run
+ * @param end the end index of the run
+ */
+ Run(GlyphVector gv, Font f, int start, int end)
+ {
+ glyphVector = gv;
+ font = f;
+ runStart = start;
+ runEnd = end;
+ }
+
+ /**
+ * Returns <code>true</code> when this run is left to right,
+ * <code>false</code> otherwise.
+ *
+ * @return <code>true</code> when this run is left to right,
+ * <code>false</code> otherwise
+ */
+ boolean isLeftToRight()
+ {
+ return (glyphVector.getLayoutFlags() & GlyphVector.FLAG_RUN_RTL) == 0;
+ }
+ }
+
+ /**
+ * The laid out character runs.
+ */
+ private Run[] runs;
+
private FontRenderContext frc;
- private String string;
+ private char[] string;
+ private int offset;
+ private int length;
private Rectangle2D boundsCache;
private LineMetrics lm;
/**
- * Start and end character indices of the runs.
- * First index is the run number, second is 0 or 1 for the starting
- * and ending character index of the run, respectively.
+ * The total advance of this text layout. This is cache for maximum
+ * performance.
+ */
+ private float totalAdvance = -1F;
+
+ /**
+ * The cached natural bounds.
*/
- private int[][] runIndices;
+ private Rectangle2D naturalBounds;
/**
* Character indices.
@@ -94,63 +161,87 @@ public final class TextLayout implements Cloneable
private Bidi bidi;
/**
+ * Mpas the logical position of each individual character in the original
+ * string to its visual position.
+ */
+ private int[] logicalToVisual;
+
+ /**
+ * Maps visual positions of a character to its logical position
+ * in the original string.
+ */
+ private int[] visualToLogical;
+
+ /**
+ * The cached hashCode.
+ */
+ private int hash;
+
+ /**
* The default caret policy.
*/
- public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY = new CaretPolicy();
+ public static final TextLayout.CaretPolicy DEFAULT_CARET_POLICY =
+ new CaretPolicy();
/**
* Constructs a TextLayout.
*/
- public TextLayout (String string, Font font, FontRenderContext frc)
+ public TextLayout (String str, Font font, FontRenderContext frc)
{
- this.font = font;
this.frc = frc;
- this.string = string;
- lm = font.getLineMetrics(string, frc);
+ string = str.toCharArray();
+ offset = 0;
+ length = this.string.length;
+ lm = font.getLineMetrics(this.string, offset, length, frc);
// Get base direction and whitespace info
getStringProperties();
- if( Bidi.requiresBidi( string.toCharArray(), 0, string.length() ) )
+ if (Bidi.requiresBidi(string, offset, offset + length))
{
- bidi = new Bidi( string, leftToRight ?
- Bidi.DIRECTION_LEFT_TO_RIGHT :
- Bidi.DIRECTION_RIGHT_TO_LEFT );
+ bidi = new Bidi(str, leftToRight ? Bidi.DIRECTION_LEFT_TO_RIGHT
+ : Bidi.DIRECTION_RIGHT_TO_LEFT );
int rc = bidi.getRunCount();
byte[] table = new byte[ rc ];
for(int i = 0; i < table.length; i++)
table[i] = (byte)bidi.getRunLevel(i);
- runs = new GlyphVector[ rc ];
- runIndices = new int[rc][2];
- for(int i = 0; i < runs.length; i++)
+ runs = new Run[rc];
+ for(int i = 0; i < rc; i++)
{
- runIndices[i][0] = bidi.getRunStart( i );
- runIndices[i][1] = bidi.getRunLimit( i );
- if( runIndices[i][0] != runIndices[i][1] ) // no empty runs.
+ int start = bidi.getRunStart(i);
+ int end = bidi.getRunLimit(i);
+ if(start != end) // no empty runs.
{
- runs[i] = font.layoutGlyphVector
- ( frc, string.toCharArray(),
- runIndices[i][0], runIndices[i][1],
- ((table[i] & 1) == 0) ? Font.LAYOUT_LEFT_TO_RIGHT :
- Font.LAYOUT_RIGHT_TO_LEFT );
- }
+ GlyphVector gv = font.layoutGlyphVector(frc,
+ string, start, end,
+ ((table[i] & 1) == 0) ? Font.LAYOUT_LEFT_TO_RIGHT
+ : Font.LAYOUT_RIGHT_TO_LEFT );
+ runs[i] = new Run(gv, font, start, end);
+ }
}
Bidi.reorderVisually( table, 0, runs, 0, runs.length );
+ // Clean up null runs.
+ ArrayList cleaned = new ArrayList(rc);
+ for (int i = 0; i < rc; i++)
+ {
+ if (runs[i] != null)
+ cleaned.add(runs[i]);
+ }
+ runs = new Run[cleaned.size()];
+ runs = (Run[]) cleaned.toArray(runs);
}
else
{
- runs = new GlyphVector[ 1 ];
- runIndices = new int[1][2];
- runIndices[0][0] = 0;
- runIndices[0][1] = string.length();
- runs[ 0 ] = font.layoutGlyphVector( frc, string.toCharArray(),
- 0, string.length(),
- leftToRight ?
- Font.LAYOUT_LEFT_TO_RIGHT :
- Font.LAYOUT_RIGHT_TO_LEFT );
+ GlyphVector gv = font.layoutGlyphVector( frc, string, offset, length,
+ leftToRight ? Font.LAYOUT_LEFT_TO_RIGHT
+ : Font.LAYOUT_RIGHT_TO_LEFT );
+ Run run = new Run(gv, font, 0, length);
+ runs = new Run[]{ run };
}
setCharIndices();
+ setupMappings();
+ layoutRuns();
}
public TextLayout (String string,
@@ -173,7 +264,6 @@ public final class TextLayout implements Cloneable
*/
TextLayout(TextLayout t, int startIndex, int endIndex)
{
- font = t.font;
frc = t.frc;
boundsCache = null;
lm = t.lm;
@@ -181,30 +271,35 @@ public final class TextLayout implements Cloneable
if( endIndex > t.getCharacterCount() )
endIndex = t.getCharacterCount();
- string = t.string.substring( startIndex, endIndex );
+ string = t.string;
+ offset = startIndex + offset;
+ length = endIndex - startIndex;
int startingRun = t.charIndices[startIndex][0];
int nRuns = 1 + t.charIndices[endIndex - 1][0] - startingRun;
- runIndices = new int[ nRuns ][2];
- runs = new GlyphVector[ nRuns ];
+ runs = new Run[nRuns];
for( int i = 0; i < nRuns; i++ )
{
- GlyphVector run = t.runs[ i + startingRun ];
+ Run run = t.runs[i + startingRun];
+ GlyphVector gv = run.glyphVector;
+ Font font = run.font;
// Copy only the relevant parts of the first and last runs.
int beginGlyphIndex = (i > 0) ? 0 : t.charIndices[startIndex][1];
- int numEntries = ( i < nRuns - 1) ? run.getNumGlyphs() :
+ int numEntries = ( i < nRuns - 1) ? gv.getNumGlyphs() :
1 + t.charIndices[endIndex - 1][1] - beginGlyphIndex;
- int[] codes = run.getGlyphCodes(beginGlyphIndex, numEntries, null);
- runs[ i ] = font.createGlyphVector( frc, codes );
- runIndices[ i ][0] = t.runIndices[i + startingRun][0] - startIndex;
- runIndices[ i ][1] = t.runIndices[i + startingRun][1] - startIndex;
+ int[] codes = gv.getGlyphCodes(beginGlyphIndex, numEntries, null);
+ gv = font.createGlyphVector(frc, codes);
+ runs[i] = new Run(gv, font, run.runStart - startIndex,
+ run.runEnd - startIndex);
}
- runIndices[ nRuns - 1 ][1] = endIndex - 1;
+ runs[nRuns - 1].runEnd = endIndex - 1;
setCharIndices();
+ setupMappings();
determineWhiteSpace();
+ layoutRuns();
}
private void setCharIndices()
@@ -215,16 +310,53 @@ public final class TextLayout implements Cloneable
for(int run = 0; run < runs.length; run++)
{
currentChar = -1;
- for( int gi = 0; gi < runs[ run ].getNumGlyphs(); gi++)
- {
- if( runs[ run ].getGlyphCharIndex( gi ) != currentChar )
- {
- charIndices[ i ][0] = run;
- charIndices[ i ][1] = gi;
- currentChar = runs[ run ].getGlyphCharIndex( gi );
- i++;
- }
- }
+ Run current = runs[run];
+ GlyphVector gv = current.glyphVector;
+ for( int gi = 0; gi < gv.getNumGlyphs(); gi++)
+ {
+ if( gv.getGlyphCharIndex( gi ) != currentChar )
+ {
+ charIndices[ i ][0] = run;
+ charIndices[ i ][1] = gi;
+ currentChar = gv.getGlyphCharIndex( gi );
+ i++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Initializes the logicalToVisual and visualToLogial maps.
+ */
+ private void setupMappings()
+ {
+ int numChars = getCharacterCount();
+ logicalToVisual = new int[numChars];
+ visualToLogical = new int[numChars];
+ int lIndex = 0;
+ int vIndex = 0;
+ // We scan the runs in visual order and set the mappings accordingly.
+ for (int i = 0; i < runs.length; i++)
+ {
+ Run run = runs[i];
+ if (run.isLeftToRight())
+ {
+ for (lIndex = run.runStart; lIndex < run.runEnd; lIndex++)
+ {
+ logicalToVisual[lIndex] = vIndex;
+ visualToLogical[vIndex] = lIndex;
+ vIndex++;
+ }
+ }
+ else
+ {
+ for (lIndex = run.runEnd - 1; lIndex >= run.runStart; lIndex--)
+ {
+ logicalToVisual[lIndex] = vIndex;
+ visualToLogical[vIndex] = lIndex;
+ vIndex++;
+ }
+ }
}
}
@@ -261,11 +393,11 @@ public final class TextLayout implements Cloneable
private void getStringProperties()
{
boolean gotDirection = false;
- int i = 0;
-
+ int i = offset;
+ int endOffs = offset + length;
leftToRight = true;
- while( i < string.length() && !gotDirection )
- switch( Character.getDirectionality( string.charAt( i++ ) ) )
+ while( i < endOffs && !gotDirection )
+ switch( Character.getDirectionality(string[i++]) )
{
case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
@@ -288,28 +420,30 @@ public final class TextLayout implements Cloneable
{
// Determine if there's whitespace in the thing.
// Ignore trailing chars.
- int i = string.length() - 1;
+ int i = offset + length - 1;
hasWhitespace = false;
- while( i >= 0 && Character.isWhitespace( string.charAt(i) ) )
+ while( i >= offset && Character.isWhitespace( string[i] ) )
i--;
// Check the remaining chars
- while( i >= 0 )
- if( Character.isWhitespace( string.charAt(i--) ) )
+ while( i >= offset )
+ if( Character.isWhitespace( string[i--] ) )
hasWhitespace = true;
}
protected Object clone ()
{
- return new TextLayout( string, font, frc );
+ return new TextLayout( this, 0, length);
}
public void draw (Graphics2D g2, float x, float y)
{
for(int i = 0; i < runs.length; i++)
{
- g2.drawGlyphVector(runs[i], x, y);
- Rectangle2D r = runs[i].getLogicalBounds();
- x += r.getWidth();
+ Run run = runs[i];
+ GlyphVector gv = run.glyphVector;
+ g2.drawGlyphVector(gv, x, y);
+ Rectangle2D r = gv.getLogicalBounds();
+ x += r.getWidth();
}
}
@@ -334,9 +468,16 @@ public final class TextLayout implements Cloneable
public float getAdvance ()
{
- float totalAdvance = 0f;
- for(int i = 0; i < runs.length; i++)
- totalAdvance += runs[i].getLogicalBounds().getWidth();
+ if (totalAdvance == -1F)
+ {
+ totalAdvance = 0f;
+ for(int i = 0; i < runs.length; i++)
+ {
+ Run run = runs[i];
+ GlyphVector gv = run.glyphVector;
+ totalAdvance += gv.getLogicalBounds().getWidth();
+ }
+ }
return totalAdvance;
}
@@ -371,27 +512,33 @@ public final class TextLayout implements Cloneable
double advance = 0;
for( int i = 0; i < ri; i++ )
- advance += runs[i].getLogicalBounds().getWidth();
+ {
+ Run run = runs[i];
+ GlyphVector gv = run.glyphVector;
+ advance += gv.getLogicalBounds().getWidth();
+ }
for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
{
+ Run run = runs[i];
+ GlyphVector gv = run.glyphVector;
int dg;
if( i == charIndices[ secondEndpoint - 1 ][0] )
dg = charIndices[ secondEndpoint - 1][1];
else
- dg = runs[i].getNumGlyphs() - 1;
+ dg = gv.getNumGlyphs() - 1;
for( int j = 0; j <= dg; j++ )
{
- Rectangle2D r2 = (runs[i].getGlyphVisualBounds( j )).
+ Rectangle2D r2 = (gv.getGlyphVisualBounds( j )).
getBounds2D();
- Point2D p = runs[i].getGlyphPosition( j );
+ Point2D p = gv.getGlyphPosition( j );
r2.setRect( advance + r2.getX(), r2.getY(),
r2.getWidth(), r2.getHeight() );
gp.append(r2, false);
}
- advance += runs[i].getLogicalBounds().getWidth();
+ advance += gv.getLogicalBounds().getWidth();
}
return gp;
}
@@ -405,47 +552,138 @@ public final class TextLayout implements Cloneable
public float[] getCaretInfo (TextHitInfo hit)
{
- return getCaretInfo(hit, getBounds());
+ return getCaretInfo(hit, getNaturalBounds());
}
public float[] getCaretInfo (TextHitInfo hit, Rectangle2D bounds)
- throws NotImplementedException
{
- throw new Error ("not implemented");
+ float[] info = new float[2];
+ int index = hit.getCharIndex();
+ boolean leading = hit.isLeadingEdge();
+ // For the boundary cases we return the boundary runs.
+ Run run;
+
+ if (index >= length)
+ {
+ info[0] = getAdvance();
+ info[1] = 0;
+ }
+ else
+ {
+ if (index < 0)
+ {
+ run = runs[0];
+ index = 0;
+ leading = true;
+ }
+ else
+ run = findRunAtIndex(index);
+
+ int glyphIndex = index - run.runStart;
+ Shape glyphBounds = run.glyphVector.getGlyphLogicalBounds(glyphIndex);
+ Rectangle2D glyphRect = glyphBounds.getBounds2D();
+ if (isVertical())
+ {
+ if (leading)
+ info[0] = (float) glyphRect.getMinY();
+ else
+ info[0] = (float) glyphRect.getMaxY();
+ }
+ else
+ {
+ if (leading)
+ info[0] = (float) glyphRect.getMinX();
+ else
+ info[0] = (float) glyphRect.getMaxX();
+ }
+ info[0] += run.location;
+ info[1] = run.font.getItalicAngle();
+ }
+ return info;
}
- public Shape getCaretShape (TextHitInfo hit)
+ public Shape getCaretShape(TextHitInfo hit)
{
- return getCaretShape( hit, getBounds() );
+ return getCaretShape(hit, getBounds());
}
- public Shape getCaretShape (TextHitInfo hit, Rectangle2D bounds)
- throws NotImplementedException
+ public Shape getCaretShape(TextHitInfo hit, Rectangle2D bounds)
{
- throw new Error ("not implemented");
+ // TODO: Handle vertical shapes somehow.
+ float[] info = getCaretInfo(hit);
+ float x1 = info[0];
+ float y1 = (float) bounds.getMinY();
+ float x2 = info[0];
+ float y2 = (float) bounds.getMaxY();
+ if (info[1] != 0)
+ {
+ // Shift x1 and x2 according to the slope.
+ x1 -= y1 * info[1];
+ x2 -= y2 * info[1];
+ }
+ GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 2);
+ path.moveTo(x1, y1);
+ path.lineTo(x2, y2);
+ return path;
}
- public Shape[] getCaretShapes (int offset)
+ public Shape[] getCaretShapes(int offset)
{
- return getCaretShapes( offset, getBounds() );
+ return getCaretShapes(offset, getNaturalBounds());
}
- public Shape[] getCaretShapes (int offset, Rectangle2D bounds)
- throws NotImplementedException
+ public Shape[] getCaretShapes(int offset, Rectangle2D bounds)
{
- throw new Error ("not implemented");
+ return getCaretShapes(offset, bounds, DEFAULT_CARET_POLICY);
+ }
+
+ public Shape[] getCaretShapes(int offset, Rectangle2D bounds,
+ CaretPolicy policy)
+ {
+ // The RI returns a 2-size array even when there's only one
+ // shape in it.
+ Shape[] carets = new Shape[2];
+ TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
+ int caretHit1 = hitToCaret(hit1);
+ TextHitInfo hit2 = hit1.getOtherHit();
+ int caretHit2 = hitToCaret(hit2);
+ if (caretHit1 == caretHit2)
+ {
+ carets[0] = getCaretShape(hit1);
+ carets[1] = null; // The RI returns null in this seldom case.
+ }
+ else
+ {
+ Shape caret1 = getCaretShape(hit1);
+ Shape caret2 = getCaretShape(hit2);
+ TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this);
+ if (strong == hit1)
+ {
+ carets[0] = caret1;
+ carets[1] = caret2;
+ }
+ else
+ {
+ carets[0] = caret2;
+ carets[1] = caret1;
+ }
+ }
+ return carets;
}
public int getCharacterCount ()
{
- return string.length();
+ return length;
}
public byte getCharacterLevel (int index)
{
+ byte level;
if( bidi == null )
- return (byte)( leftToRight ? 0 : 1 );
- return (byte)bidi.getLevelAt( index );
+ level = 0;
+ else
+ level = (byte) bidi.getLevelAt(index);
+ return level;
}
public float getDescent ()
@@ -490,19 +728,21 @@ public final class TextLayout implements Cloneable
double advance = 0;
for( int i = 0; i < ri; i++ )
- advance += runs[i].getLogicalBounds().getWidth();
+ advance += runs[i].glyphVector.getLogicalBounds().getWidth();
for( int i = ri; i <= charIndices[ secondEndpoint - 1 ][0]; i++ )
{
+ Run run = runs[i];
+ GlyphVector gv = run.glyphVector;
int dg; // last index in this run to use.
if( i == charIndices[ secondEndpoint - 1 ][0] )
dg = charIndices[ secondEndpoint - 1][1];
else
- dg = runs[i].getNumGlyphs() - 1;
+ dg = gv.getNumGlyphs() - 1;
for(; gi <= dg; gi++ )
{
- Rectangle2D r2 = (runs[i].getGlyphLogicalBounds( gi )).
+ Rectangle2D r2 = (gv.getGlyphLogicalBounds( gi )).
getBounds2D();
if( r == null )
r = r2;
@@ -511,7 +751,7 @@ public final class TextLayout implements Cloneable
}
gi = 0; // reset glyph index into run for next run.
- advance += runs[i].getLogicalBounds().getWidth();
+ advance += gv.getLogicalBounds().getWidth();
}
return r;
@@ -519,33 +759,137 @@ public final class TextLayout implements Cloneable
public int[] getLogicalRangesForVisualSelection (TextHitInfo firstEndpoint,
TextHitInfo secondEndpoint)
- throws NotImplementedException
{
- throw new Error ("not implemented");
+ // Check parameters.
+ checkHitInfo(firstEndpoint);
+ checkHitInfo(secondEndpoint);
+
+ // Convert to visual and order correctly.
+ int start = hitToCaret(firstEndpoint);
+ int end = hitToCaret(secondEndpoint);
+ if (start > end)
+ {
+ // Swap start and end so that end >= start.
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+
+ // Now walk through the visual indices and mark the included pieces.
+ boolean[] include = new boolean[length];
+ for (int i = start; i < end; i++)
+ {
+ include[visualToLogical[i]] = true;
+ }
+
+ // Count included runs.
+ int numRuns = 0;
+ boolean in = false;
+ for (int i = 0; i < length; i++)
+ {
+ if (include[i] != in) // At each run in/out point we toggle the in var.
+ {
+ in = ! in;
+ if (in) // At each run start we count up.
+ numRuns++;
+ }
+ }
+
+ // Put together the ranges array.
+ int[] ranges = new int[numRuns * 2];
+ int index = 0;
+ in = false;
+ for (int i = 0; i < length; i++)
+ {
+ if (include[i] != in)
+ {
+ ranges[index] = i;
+ index++;
+ in = ! in;
+ }
+ }
+ // If the last run ends at the very end, include that last bit too.
+ if (in)
+ ranges[index] = length;
+
+ return ranges;
}
- public TextHitInfo getNextLeftHit (int offset)
- throws NotImplementedException
+ public TextHitInfo getNextLeftHit(int offset)
{
- throw new Error ("not implemented");
+ return getNextLeftHit(offset, DEFAULT_CARET_POLICY);
+ }
+
+ public TextHitInfo getNextLeftHit(int offset, CaretPolicy policy)
+ {
+ if (policy == null)
+ throw new IllegalArgumentException("Null policy not allowed");
+ if (offset < 0 || offset > length)
+ throw new IllegalArgumentException("Offset out of bounds");
+
+ TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
+ TextHitInfo hit2 = hit1.getOtherHit();
+
+ TextHitInfo strong = policy.getStrongCaret(hit1, hit2, this);
+ TextHitInfo next = getNextLeftHit(strong);
+ TextHitInfo ret = null;
+ if (next != null)
+ {
+ TextHitInfo next2 = getVisualOtherHit(next);
+ ret = policy.getStrongCaret(next2, next, this);
+ }
+ return ret;
}
public TextHitInfo getNextLeftHit (TextHitInfo hit)
- throws NotImplementedException
{
- throw new Error ("not implemented");
+ checkHitInfo(hit);
+ int index = hitToCaret(hit);
+ TextHitInfo next = null;
+ if (index != 0)
+ {
+ index--;
+ next = caretToHit(index);
+ }
+ return next;
+ }
+
+ public TextHitInfo getNextRightHit(int offset)
+ {
+ return getNextRightHit(offset, DEFAULT_CARET_POLICY);
}
- public TextHitInfo getNextRightHit (int offset)
- throws NotImplementedException
+ public TextHitInfo getNextRightHit(int offset, CaretPolicy policy)
{
- throw new Error ("not implemented");
+ if (policy == null)
+ throw new IllegalArgumentException("Null policy not allowed");
+ if (offset < 0 || offset > length)
+ throw new IllegalArgumentException("Offset out of bounds");
+
+ TextHitInfo hit1 = TextHitInfo.afterOffset(offset);
+ TextHitInfo hit2 = hit1.getOtherHit();
+
+ TextHitInfo next = getNextRightHit(policy.getStrongCaret(hit1, hit2, this));
+ TextHitInfo ret = null;
+ if (next != null)
+ {
+ TextHitInfo next2 = getVisualOtherHit(next);
+ ret = policy.getStrongCaret(next2, next, this);
+ }
+ return ret;
}
- public TextHitInfo getNextRightHit (TextHitInfo hit)
- throws NotImplementedException
+ public TextHitInfo getNextRightHit(TextHitInfo hit)
{
- throw new Error ("not implemented");
+ checkHitInfo(hit);
+ int index = hitToCaret(hit);
+ TextHitInfo next = null;
+ if (index < length)
+ {
+ index++;
+ next = caretToHit(index);
+ }
+ return next;
}
public Shape getOutline (AffineTransform tx)
@@ -554,8 +898,9 @@ public final class TextLayout implements Cloneable
GeneralPath gp = new GeneralPath();
for(int i = 0; i < runs.length; i++)
{
- gp.append( runs[i].getOutline( x, 0f ), false );
- Rectangle2D r = runs[i].getLogicalBounds();
+ GlyphVector gv = runs[i].glyphVector;
+ gp.append( gv.getOutline( x, 0f ), false );
+ Rectangle2D r = gv.getLogicalBounds();
x += r.getWidth();
}
if( tx != null )
@@ -571,27 +916,28 @@ public final class TextLayout implements Cloneable
return 0f;
// No trailing whitespace
- if( !Character.isWhitespace( string.charAt( string.length() -1 ) ) )
+ if( !Character.isWhitespace( string[offset + length - 1]) )
return getAdvance();
// Get length of all runs up to the last
for(int i = 0; i < runs.length - 1; i++)
- totalAdvance += runs[i].getLogicalBounds().getWidth();
+ totalAdvance += runs[i].glyphVector.getLogicalBounds().getWidth();
- int lastRun = runIndices[ runs.length - 1 ][0];
- int j = string.length() - 1;
- while( j >= lastRun && Character.isWhitespace( string.charAt( j ) ) ) j--;
+ int lastRun = runs[runs.length - 1].runStart;
+ int j = length - 1;
+ while( j >= lastRun && Character.isWhitespace( string[j] ) ) j--;
if( j < lastRun )
return totalAdvance; // entire last run is whitespace
int lastNonWSChar = j - lastRun;
j = 0;
- while( runs[ runs.length - 1 ].getGlyphCharIndex( j )
+ while( runs[ runs.length - 1 ].glyphVector.getGlyphCharIndex( j )
<= lastNonWSChar )
{
- totalAdvance += runs[ runs.length - 1 ].getGlyphLogicalBounds( j ).
- getBounds2D().getWidth();
+ totalAdvance += runs[ runs.length - 1 ].glyphVector
+ .getGlyphLogicalBounds( j )
+ .getBounds2D().getWidth();
j ++;
}
@@ -608,15 +954,129 @@ public final class TextLayout implements Cloneable
public Shape getVisualHighlightShape (TextHitInfo firstEndpoint,
TextHitInfo secondEndpoint,
Rectangle2D bounds)
- throws NotImplementedException
{
- throw new Error ("not implemented");
+ GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+ Shape caret1 = getCaretShape(firstEndpoint, bounds);
+ path.append(caret1, false);
+ Shape caret2 = getCaretShape(secondEndpoint, bounds);
+ path.append(caret2, false);
+ // Append left (top) bounds to selection if necessary.
+ int c1 = hitToCaret(firstEndpoint);
+ int c2 = hitToCaret(secondEndpoint);
+ if (c1 == 0 || c2 == 0)
+ {
+ path.append(left(bounds), false);
+ }
+ // Append right (bottom) bounds if necessary.
+ if (c1 == length || c2 == length)
+ {
+ path.append(right(bounds), false);
+ }
+ return path.getBounds2D();
+ }
+
+ /**
+ * Returns the shape that makes up the left (top) edge of this text layout.
+ *
+ * @param b the bounds
+ *
+ * @return the shape that makes up the left (top) edge of this text layout
+ */
+ private Shape left(Rectangle2D b)
+ {
+ GeneralPath left = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+ left.append(getCaretShape(TextHitInfo.beforeOffset(0)), false);
+ if (isVertical())
+ {
+ float y = (float) b.getMinY();
+ left.append(new Line2D.Float((float) b.getMinX(), y,
+ (float) b.getMaxX(), y), false);
+ }
+ else
+ {
+ float x = (float) b.getMinX();
+ left.append(new Line2D.Float(x, (float) b.getMinY(),
+ x, (float) b.getMaxY()), false);
+ }
+ return left.getBounds2D();
+ }
+
+ /**
+ * Returns the shape that makes up the right (bottom) edge of this text
+ * layout.
+ *
+ * @param b the bounds
+ *
+ * @return the shape that makes up the right (bottom) edge of this text
+ * layout
+ */
+ private Shape right(Rectangle2D b)
+ {
+ GeneralPath right = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
+ right.append(getCaretShape(TextHitInfo.afterOffset(length)), false);
+ if (isVertical())
+ {
+ float y = (float) b.getMaxY();
+ right.append(new Line2D.Float((float) b.getMinX(), y,
+ (float) b.getMaxX(), y), false);
+ }
+ else
+ {
+ float x = (float) b.getMaxX();
+ right.append(new Line2D.Float(x, (float) b.getMinY(),
+ x, (float) b.getMaxY()), false);
+ }
+ return right.getBounds2D();
}
public TextHitInfo getVisualOtherHit (TextHitInfo hit)
- throws NotImplementedException
{
- throw new Error ("not implemented");
+ checkHitInfo(hit);
+ int hitIndex = hit.getCharIndex();
+
+ int index;
+ boolean leading;
+ if (hitIndex == -1 || hitIndex == length)
+ {
+ // Boundary case.
+ int visual;
+ if (isLeftToRight() == (hitIndex == -1))
+ visual = 0;
+ else
+ visual = length - 1;
+ index = visualToLogical[visual];
+ if (isLeftToRight() == (hitIndex == -1))
+ leading = isCharacterLTR(index); // LTR.
+ else
+ leading = ! isCharacterLTR(index); // RTL.
+ }
+ else
+ {
+ // Normal case.
+ int visual = logicalToVisual[hitIndex];
+ boolean b;
+ if (isCharacterLTR(hitIndex) == hit.isLeadingEdge())
+ {
+ visual--;
+ b = false;
+ }
+ else
+ {
+ visual++;
+ b = true;
+ }
+ if (visual >= 0 && visual < length)
+ {
+ index = visualToLogical[visual];
+ leading = b == isLeftToRight();
+ }
+ else
+ {
+ index = b == isLeftToRight() ? length : -1;
+ leading = index == length;
+ }
+ }
+ return leading ? TextHitInfo.leading(index) : TextHitInfo.trailing(index);
}
/**
@@ -631,16 +1091,19 @@ public final class TextLayout implements Cloneable
int nglyphs = 0; // # of whitespace chars
// determine last non-whitespace char.
- int lastNWS = string.length() - 1;
- while( Character.isWhitespace( string.charAt( lastNWS ) ) ) lastNWS--;
+ int lastNWS = offset + length - 1;
+ while( Character.isWhitespace( string[lastNWS] ) ) lastNWS--;
// locations of the glyphs.
- int[] wsglyphs = new int[string.length() * 10];
+ int[] wsglyphs = new int[length * 10];
for(int run = 0; run < runs.length; run++ )
- for(int i = 0; i < runs[run].getNumGlyphs(); i++ )
+ {
+ Run current = runs[run];
+ for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ )
{
- int cindex = runIndices[run][0] + runs[run].getGlyphCharIndex( i );
- if( Character.isWhitespace( string.charAt( cindex ) ) )
+ int cindex = current.runStart
+ + current.glyphVector.getGlyphCharIndex( i );
+ if( Character.isWhitespace( string[cindex] ) )
// && cindex < lastNWS )
{
wsglyphs[ nglyphs * 2 ] = run;
@@ -648,34 +1111,148 @@ public final class TextLayout implements Cloneable
nglyphs++;
}
}
-
+ }
deltaW = deltaW / nglyphs; // Change in width per whitespace glyph
double w = 0;
int cws = 0;
// Shift all characters
for(int run = 0; run < runs.length; run++ )
- for(int i = 0; i < runs[ run ].getNumGlyphs(); i++ )
- {
- if( wsglyphs[ cws * 2 ] == run && wsglyphs[ cws * 2 + 1 ] == i )
- {
- cws++; // update 'current whitespace'
- w += deltaW; // increment the shift
- }
- Point2D p = runs[ run ].getGlyphPosition( i );
- p.setLocation( p.getX() + w, p.getY() );
- runs[ run ].setGlyphPosition( i, p );
- }
+ {
+ Run current = runs[run];
+ for(int i = 0; i < current.glyphVector.getNumGlyphs(); i++ )
+ {
+ if( wsglyphs[ cws * 2 ] == run && wsglyphs[ cws * 2 + 1 ] == i )
+ {
+ cws++; // update 'current whitespace'
+ w += deltaW; // increment the shift
+ }
+ Point2D p = current.glyphVector.getGlyphPosition( i );
+ p.setLocation( p.getX() + w, p.getY() );
+ current.glyphVector.setGlyphPosition( i, p );
+ }
+ }
}
public TextHitInfo hitTestChar (float x, float y)
{
- return hitTestChar(x, y, getBounds());
+ return hitTestChar(x, y, getNaturalBounds());
}
+ /**
+ * Finds the character hit at the specified point. This 'clips' this
+ * text layout against the specified <code>bounds</code> rectangle. That
+ * means that in the case where a point is outside these bounds, this method
+ * returns the leading edge of the first character or the trailing edge of
+ * the last character.
+ *
+ * @param x the X location to test
+ * @param y the Y location to test
+ * @param bounds the bounds to test against
+ *
+ * @return the character hit at the specified point
+ */
public TextHitInfo hitTestChar (float x, float y, Rectangle2D bounds)
- throws NotImplementedException
{
- throw new Error ("not implemented");
+ // Check bounds.
+ if (isVertical())
+ {
+ if (y < bounds.getMinY())
+ return TextHitInfo.leading(0);
+ else if (y > bounds.getMaxY())
+ return TextHitInfo.trailing(getCharacterCount() - 1);
+ }
+ else
+ {
+ if (x < bounds.getMinX())
+ return TextHitInfo.leading(0);
+ else if (x > bounds.getMaxX())
+ return TextHitInfo.trailing(getCharacterCount() - 1);
+ }
+
+ TextHitInfo hitInfo = null;
+ if (isVertical())
+ {
+ // Search for the run at the location.
+ // TODO: Perform binary search for maximum efficiency. However, we
+ // need the run location laid out statically to do that.
+ int numRuns = runs.length;
+ float offs = 0;
+ Run hitRun = null;
+ for (int i = 0; i < numRuns && hitRun == null; i++)
+ {
+ Run run = runs[i];
+ Rectangle2D lBounds = run.glyphVector.getLogicalBounds();
+ if (lBounds.getMinY() + offs <= y && lBounds.getMaxY() + offs > y)
+ hitRun = run;
+ else
+ offs += lBounds.getHeight();
+ }
+ // Now we have (hopefully) found a run that hits. Now find the
+ // right character.
+ if (hitRun != null)
+ {
+ GlyphVector gv = hitRun.glyphVector;
+ for (int i = hitRun.runStart;
+ i < hitRun.runEnd && hitInfo == null; i++)
+ {
+ int gi = i - hitRun.runStart;
+ Rectangle2D lBounds = gv.getGlyphLogicalBounds(gi)
+ .getBounds2D();
+ if (lBounds.getMinY() + offs <= y
+ && lBounds.getMaxY() + offs > y)
+ {
+ // Found hit. Now check if we are leading or trailing.
+ boolean leading = true;
+ if (lBounds.getCenterY() + offs <= y)
+ leading = false;
+ hitInfo = leading ? TextHitInfo.leading(i)
+ : TextHitInfo.trailing(i);
+ }
+ }
+ }
+ }
+ else
+ {
+ // Search for the run at the location.
+ // TODO: Perform binary search for maximum efficiency. However, we
+ // need the run location laid out statically to do that.
+ int numRuns = runs.length;
+ float offs = 0;
+ Run hitRun = null;
+ for (int i = 0; i < numRuns && hitRun == null; i++)
+ {
+ Run run = runs[i];
+ Rectangle2D lBounds = run.glyphVector.getLogicalBounds();
+ if (lBounds.getMinX() + offs <= x && lBounds.getMaxX() + offs > x)
+ hitRun = run;
+ else
+ offs += lBounds.getWidth();
+ }
+ // Now we have (hopefully) found a run that hits. Now find the
+ // right character.
+ if (hitRun != null)
+ {
+ GlyphVector gv = hitRun.glyphVector;
+ for (int i = hitRun.runStart;
+ i < hitRun.runEnd && hitInfo == null; i++)
+ {
+ int gi = i - hitRun.runStart;
+ Rectangle2D lBounds = gv.getGlyphLogicalBounds(gi)
+ .getBounds2D();
+ if (lBounds.getMinX() + offs <= x
+ && lBounds.getMaxX() + offs > x)
+ {
+ // Found hit. Now check if we are leading or trailing.
+ boolean leading = true;
+ if (lBounds.getCenterX() + offs <= x)
+ leading = false;
+ hitInfo = leading ? TextHitInfo.leading(i)
+ : TextHitInfo.trailing(i);
+ }
+ }
+ }
+ }
+ return hitInfo;
}
public boolean isLeftToRight ()
@@ -689,18 +1266,127 @@ public final class TextLayout implements Cloneable
}
public int hashCode ()
- throws NotImplementedException
{
- throw new Error ("not implemented");
+ // This is implemented in sync to equals().
+ if (hash == 0 && runs.length > 0)
+ {
+ hash = runs.length;
+ for (int i = 0; i < runs.length; i++)
+ hash ^= runs[i].glyphVector.hashCode();
+ }
+ return hash;
}
public String toString ()
{
- return "TextLayout [string:"+string+", Font:"+font+" Rendercontext:"+
+ return "TextLayout [string:"+ new String(string, offset, length)
+ +" Rendercontext:"+
frc+"]";
}
/**
+ * Returns the natural bounds of that text layout. This is made up
+ * of the ascent plus descent and the text advance.
+ *
+ * @return the natural bounds of that text layout
+ */
+ private Rectangle2D getNaturalBounds()
+ {
+ if (naturalBounds == null)
+ naturalBounds = new Rectangle2D.Float(0.0F, -getAscent(), getAdvance(),
+ getAscent() + getDescent());
+ return naturalBounds;
+ }
+
+ private void checkHitInfo(TextHitInfo hit)
+ {
+ if (hit == null)
+ throw new IllegalArgumentException("Null hit info not allowed");
+ int index = hit.getInsertionIndex();
+ if (index < 0 || index > length)
+ throw new IllegalArgumentException("Hit index out of range");
+ }
+
+ private int hitToCaret(TextHitInfo hit)
+ {
+ int index = hit.getCharIndex();
+ int ret;
+ if (index < 0)
+ ret = isLeftToRight() ? 0 : length;
+ else if (index >= length)
+ ret = isLeftToRight() ? length : 0;
+ else
+ {
+ ret = logicalToVisual[index];
+ if (hit.isLeadingEdge() != isCharacterLTR(index))
+ ret++;
+ }
+ return ret;
+ }
+
+ private TextHitInfo caretToHit(int index)
+ {
+ TextHitInfo hit;
+ if (index == 0 || index == length)
+ {
+ if ((index == length) == isLeftToRight())
+ hit = TextHitInfo.leading(length);
+ else
+ hit = TextHitInfo.trailing(-1);
+ }
+ else
+ {
+ int logical = visualToLogical[index];
+ boolean leading = isCharacterLTR(logical); // LTR.
+ hit = leading ? TextHitInfo.leading(logical)
+ : TextHitInfo.trailing(logical);
+ }
+ return hit;
+ }
+
+ private boolean isCharacterLTR(int index)
+ {
+ byte level = getCharacterLevel(index);
+ return (level & 1) == 0;
+ }
+
+ /**
+ * Finds the run that holds the specified (logical) character index. This
+ * returns <code>null</code> when the index is not inside the range.
+ *
+ * @param index the index of the character to find
+ *
+ * @return the run that holds the specified character
+ */
+ private Run findRunAtIndex(int index)
+ {
+ Run found = null;
+ // TODO: Can we do better than linear searching here?
+ for (int i = 0; i < runs.length && found == null; i++)
+ {
+ Run run = runs[i];
+ if (run.runStart <= index && run.runEnd > index)
+ found = run;
+ }
+ return found;
+ }
+
+ /**
+ * Computes the layout locations for each run.
+ */
+ private void layoutRuns()
+ {
+ float loc = 0.0F;
+ float lastWidth = 0.0F;
+ for (int i = 0; i < runs.length; i++)
+ {
+ runs[i].location = loc;
+ Rectangle2D bounds = runs[i].glyphVector.getLogicalBounds();
+ loc += isVertical() ? bounds.getHeight() : bounds.getWidth();
+ }
+ }
+
+ /**
* Inner class describing a caret policy
*/
public static class CaretPolicy
@@ -712,9 +1398,25 @@ public final class TextLayout implements Cloneable
public TextHitInfo getStrongCaret(TextHitInfo hit1,
TextHitInfo hit2,
TextLayout layout)
- throws NotImplementedException
{
- throw new Error ("not implemented");
+ byte l1 = layout.getCharacterLevel(hit1.getCharIndex());
+ byte l2 = layout.getCharacterLevel(hit2.getCharIndex());
+ TextHitInfo strong;
+ if (l1 == l2)
+ {
+ if (hit2.isLeadingEdge() && ! hit1.isLeadingEdge())
+ strong = hit2;
+ else
+ strong = hit1;
+ }
+ else
+ {
+ if (l1 < l2)
+ strong = hit1;
+ else
+ strong = hit2;
+ }
+ return strong;
}
}
}
diff --git a/java/awt/image/ImageFilter.java b/java/awt/image/ImageFilter.java
index d5d4f9cd9..0ead45a4a 100644
--- a/java/awt/image/ImageFilter.java
+++ b/java/awt/image/ImageFilter.java
@@ -49,181 +49,178 @@ import java.util.Hashtable;
*/
public class ImageFilter implements ImageConsumer, Cloneable
{
- /**
- * The consumer this filter is filtering an image data stream for.
- * It is initialized in the method <code>getFilterInstance</code>.
- */
- protected ImageConsumer consumer = null;
-
- /**
- * The <code>ImageConsumer</code> can use this method to request
- * the pixels be delivered in top-down, left-right order.
- * <br>
- * The filter can respond in three different ways.
- * <ul>
- * <li>The default behavior is to forward the request to the
- * <code>ImageProducer</code>
- * using the method <code>requestTopDownLeftRightResend</code>
- * and using the filter as the consumer.</li>
- * <li>The filter has the pixels and can retransmit them in the
- * top-down, left-right order.</li>
- * <li>The filter can do nothing when this method is called.</li>
- * </ul>
- */
- public void resendTopDownLeftRight(ImageProducer ip)
- {
- ip.requestTopDownLeftRightResend(this);
- }
-
- /**
- * By default, returns a shallow copy of the object created by
- * <code>Object.clone()</code>
- *
- * @see java.lang.Object#clone ()
- */
- public Object clone()
- {
- try
- {
- return super.clone();
- }
- catch (CloneNotSupportedException e)
- {
- // This should never happen as this class implements the
- // Cloneable interface.
- throw new InternalError ();
- }
- }
-
- /**
- * This is the only method which can set the
- * <code>ImageConsumer</code> for this filter. By default a clone
- * of this filter with the appropriate consumer set is returned.
- *
- * @see #clone ()
- */
- public ImageFilter getFilterInstance(ImageConsumer ic)
- {
- if ( ic == null )
- throw new IllegalArgumentException("null argument for ImageFilter.getFilterInstance(ImageConsumer)");
-
- consumer = ic;
- ImageFilter f = (ImageFilter)clone();
- consumer = null;
- return f;
- }
-
- /**
- * An <code>ImageProducer</code> indicates the size of the image
- * being produced using this method. A filter can override this
- * method to intercept these calls from the producer in order to
- * change either the width or the height before in turn calling
- * the consumer's <code>setDimensions</code> method.
- *
- * @param width the width of the image
- * @param height the height of the image
- */
- public void setDimensions(int width, int height)
- {
- if (consumer != null)
- consumer.setDimensions(width, height);
- }
-
- /**
- * An <code>ImageProducer</code> can set a list of properties
- * associated with this image by using this method.
- *
- * @param props the list of properties associated with this image
- */
- public void setProperties(Hashtable<?,?> props)
- {
- Hashtable<Object,Object> props2 = (Hashtable<Object,Object>) props;
- props2.put("filters", "ImageFilter");
- if (consumer != null)
- consumer.setProperties(props);
- }
-
- /**
- * Override this method to process calls to this method from the
- * <code>ImageProducer</code>. By default the <code>setColorModel</code>
- * method of the consumer is called with the specified <code>model</code>.
- *
- * @param model the color model to be used most often by setPixels
- * @see ColorModel */
- public void setColorModel(ColorModel model)
- {
- if (consumer != null)
- consumer.setColorModel(model);
- }
-
- /**
- * The <code>ImageProducer</code> should call this method with a
- * bit mask of hints from any of <code>RANDOMPIXELORDER</code>,
- * <code>TOPDOWNLEFTRIGHT</code>, <code>COMPLETESCANLINES</code>,
- * <code>SINGLEPASS</code>, <code>SINGLEFRAME</code> from the
- * <code>ImageConsumer</code> interface.
- *
- * @param flags a bit mask of hints
- * @see ImageConsumer
- */
- public void setHints(int flags)
- {
- if (consumer != null)
- consumer.setHints(flags);
- }
-
- /**
- * This function delivers a rectangle of pixels where any
- * pixel(m,n) is stored in the array as a <code>byte</code> at
- * index (n * scansize + m + offset).
- *
- * @param x the x coordinate of the rectangle
- * @param y the y coordinate of the rectangle
- * @param w the width of the rectangle
- * @param h the height of the rectangle
- * @param model the <code>ColorModel</code> used to translate the pixels
- * @param pixels the array of pixel values
- * @param offset the index of the first pixels in the <code>pixels</code> array
- * @param scansize the width to use in extracting pixels from the <code>pixels</code> array
- */
- public void setPixels(int x, int y, int w, int h,
- ColorModel model, byte[] pixels, int offset, int scansize)
- {
- if (consumer != null)
- consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
- }
-
- /**
- * This function delivers a rectangle of pixels where any
- * pixel(m,n) is stored in the array as an <code>int</code> at
- * index (n * scansize + m + offset).
- *
- * @param x the x coordinate of the rectangle
- * @param y the y coordinate of the rectangle
- * @param w the width of the rectangle
- * @param h the height of the rectangle
- * @param model the <code>ColorModel</code> used to translate the pixels
- * @param pixels the array of pixel values
- * @param offset the index of the first pixels in the <code>pixels</code> array
- * @param scansize the width to use in extracting pixels from the <code>pixels</code> array
- */
- public void setPixels(int x, int y, int w, int h,
- ColorModel model, int[] pixels, int offset, int scansize)
- {
- if (consumer != null)
- consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
- }
-
- /**
- * The <code>ImageProducer</code> calls this method to indicate a
- * single frame or the entire image is complete. The method is
- * also used to indicate an error in loading or producing the
- * image.
- */
- public void imageComplete(int status)
- {
- if (consumer != null)
- consumer.imageComplete(status);
- }
+ /**
+ * The consumer this filter is filtering an image data stream for.
+ * It is initialized in the method <code>getFilterInstance</code>.
+ */
+ protected ImageConsumer consumer = null;
+
+ /**
+ * The <code>ImageConsumer</code> can use this method to request
+ * the pixels be delivered in top-down, left-right order.
+ * <br>
+ * The filter can respond in three different ways.
+ * <ul>
+ * <li>The default behavior is to forward the request to the
+ * <code>ImageProducer</code>
+ * using the method <code>requestTopDownLeftRightResend</code>
+ * and using the filter as the consumer.</li>
+ * <li>The filter has the pixels and can retransmit them in the
+ * top-down, left-right order.</li>
+ * <li>The filter can do nothing when this method is called.</li>
+ * </ul>
+ */
+ public void resendTopDownLeftRight(ImageProducer ip)
+ {
+ ip.requestTopDownLeftRightResend(this);
+ }
+
+ /**
+ * By default, returns a shallow copy of the object created by
+ * <code>Object.clone()</code>
+ *
+ * @see java.lang.Object#clone ()
+ */
+ public Object clone()
+ {
+ try
+ {
+ return super.clone();
+ }
+ catch (CloneNotSupportedException e)
+ {
+ // This should never happen as this class implements the
+ // Cloneable interface.
+ throw new InternalError ();
+ }
+ }
+
+ /**
+ * This is the only method which can set the
+ * <code>ImageConsumer</code> for this filter. By default a clone
+ * of this filter with the appropriate consumer set is returned.
+ *
+ * @see #clone ()
+ */
+ public ImageFilter getFilterInstance(ImageConsumer ic)
+ {
+ ImageFilter f = (ImageFilter)clone();
+ f.consumer = ic;
+ return f;
+ }
+
+ /**
+ * An <code>ImageProducer</code> indicates the size of the image
+ * being produced using this method. A filter can override this
+ * method to intercept these calls from the producer in order to
+ * change either the width or the height before in turn calling
+ * the consumer's <code>setDimensions</code> method.
+ *
+ * @param width the width of the image
+ * @param height the height of the image
+ */
+ public void setDimensions(int width, int height)
+ {
+ consumer.setDimensions(width, height);
+ }
+
+ /**
+ * An <code>ImageProducer</code> can set a list of properties
+ * associated with this image by using this method.
+ *
+ * @param props the list of properties associated with this image
+ */
+ public void setProperties(Hashtable<?,?> props)
+ {
+ Hashtable copy = (Hashtable) props.clone();
+ Object o = copy.get("filters");
+ if (o == null)
+ copy.put("filters", toString());
+ else if (o instanceof String)
+ copy.put("filters", ((String) o) + toString());
+
+ consumer.setProperties(copy);
+ }
+
+ /**
+ * Override this method to process calls to this method from the
+ * <code>ImageProducer</code>. By default the <code>setColorModel</code>
+ * method of the consumer is called with the specified <code>model</code>.
+ *
+ * @param model the color model to be used most often by setPixels
+ *
+ * @see ColorModel
+ */
+ public void setColorModel(ColorModel model)
+ {
+ consumer.setColorModel(model);
+ }
+
+ /**
+ * The <code>ImageProducer</code> should call this method with a
+ * bit mask of hints from any of <code>RANDOMPIXELORDER</code>,
+ * <code>TOPDOWNLEFTRIGHT</code>, <code>COMPLETESCANLINES</code>,
+ * <code>SINGLEPASS</code>, <code>SINGLEFRAME</code> from the
+ * <code>ImageConsumer</code> interface.
+ *
+ * @param flags a bit mask of hints
+ * @see ImageConsumer
+ */
+ public void setHints(int flags)
+ {
+ consumer.setHints(flags);
+ }
+
+ /**
+ * This function delivers a rectangle of pixels where any
+ * pixel(m,n) is stored in the array as a <code>byte</code> at
+ * index (n * scansize + m + offset).
+ *
+ * @param x the x coordinate of the rectangle
+ * @param y the y coordinate of the rectangle
+ * @param w the width of the rectangle
+ * @param h the height of the rectangle
+ * @param model the <code>ColorModel</code> used to translate the pixels
+ * @param pixels the array of pixel values
+ * @param offset the index of the first pixels in the <code>pixels</code> array
+ * @param scansize the width to use in extracting pixels from the <code>pixels</code> array
+ */
+ public void setPixels(int x, int y, int w, int h,
+ ColorModel model, byte[] pixels, int offset,
+ int scansize)
+ {
+ consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
+ }
+
+ /**
+ * This function delivers a rectangle of pixels where any
+ * pixel(m,n) is stored in the array as an <code>int</code> at
+ * index (n * scansize + m + offset).
+ *
+ * @param x the x coordinate of the rectangle
+ * @param y the y coordinate of the rectangle
+ * @param w the width of the rectangle
+ * @param h the height of the rectangle
+ * @param model the <code>ColorModel</code> used to translate the pixels
+ * @param pixels the array of pixel values
+ * @param offset the index of the first pixels in the <code>pixels</code> array
+ * @param scansize the width to use in extracting pixels from the <code>pixels</code> array
+ */
+ public void setPixels(int x, int y, int w, int h,
+ ColorModel model, int[] pixels, int offset,
+ int scansize)
+ {
+ consumer.setPixels(x, y, w, h, model, pixels, offset, scansize);
+ }
+
+ /**
+ * The <code>ImageProducer</code> calls this method to indicate a
+ * single frame or the entire image is complete. The method is
+ * also used to indicate an error in loading or producing the
+ * image.
+ */
+ public void imageComplete(int status)
+ {
+ consumer.imageComplete(status);
+ }
}
-
diff --git a/java/awt/image/IndexColorModel.java b/java/awt/image/IndexColorModel.java
index 701362d53..46879cc98 100644
--- a/java/awt/image/IndexColorModel.java
+++ b/java/awt/image/IndexColorModel.java
@@ -134,10 +134,6 @@ public class IndexColorModel extends ColorModel
if (size < 1)
throw new IllegalArgumentException("size < 1");
map_size = size;
- if (0 <= trans && trans < size) {
- this.trans = trans;
- transparency = BITMASK;
- }
rgb = new int[size];
for (int i = 0; i < size; i++)
{
@@ -146,6 +142,9 @@ public class IndexColorModel extends ColorModel
| ((greens[i] & 0xff) << 8)
| (blues[i] & 0xff));
}
+
+ setTransparentPixel(trans);
+
// Generate a bigint with 1's for every pixel
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
}
@@ -275,8 +274,6 @@ public class IndexColorModel extends ColorModel
throw new IllegalArgumentException("size < 1");
map_size = size;
opaque = !hasAlpha;
- if (0 <= trans && trans < size)
- this.trans = trans;
rgb = new int[size];
if (hasAlpha)
@@ -318,6 +315,8 @@ public class IndexColorModel extends ColorModel
transparency = BITMASK;
}
+ setTransparentPixel(trans);
+
// Generate a bigint with 1's for every pixel
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
}
@@ -361,9 +360,6 @@ public class IndexColorModel extends ColorModel
throw new IllegalArgumentException("size < 1");
map_size = size;
opaque = !hasAlpha;
- if (0 <= trans && trans < size)
- this.trans = trans;
-
rgb = new int[size];
if (!hasAlpha)
for (int i = 0; i < size; i++)
@@ -371,6 +367,8 @@ public class IndexColorModel extends ColorModel
else
System.arraycopy(cmap, start, rgb, 0, size);
+ setTransparentPixel(trans);
+
// Generate a bigint with 1's for every pixel
validBits = validBits.setBit(size).subtract(BigInteger.ONE);
}
@@ -584,12 +582,7 @@ public class IndexColorModel extends ColorModel
*/
public final int getAlpha(int pixel)
{
- if (opaque && pixel != trans)
- return 255;
- if ((pixel == trans && trans != -1) || pixel >= map_size)
- return 0;
-
- return (0xFF000000 & rgb[pixel]) >> 24;
+ return (rgb[pixel] >> 24) & 0xFF;
}
/**
@@ -694,21 +687,43 @@ public class IndexColorModel extends ColorModel
return im;
}
-
- public ColorModel coerceData (WritableRaster raster,
- boolean isAlphaPremultiplied)
+
+ /**
+ * Creates a {@link SampleModel} that is compatible to this color model.
+ * This will be a {@link MultiPixelPackedSampleModel} for bits/pixel of
+ * 1, 2 or 4, or a {@link ComponentColorModel} for the other cases.
+ *
+ * @param w the width of the sample model to create
+ * @param h the height of the sample model to create
+ *
+ * @return a compatible sample model
+ */
+ public SampleModel createCompatibleSampleModel(int w, int h)
{
- if (this.isAlphaPremultiplied == isAlphaPremultiplied || !hasAlpha())
- return this;
-
- /* TODO: provide better implementation based on the
- assumptions we can make due to the specific type of the
- color model. */
- super.coerceDataWorker(raster, isAlphaPremultiplied);
-
- ColorModel cm = new IndexColorModel(pixel_bits, map_size, rgb, 0, hasAlpha, trans,
- transferType);
- cm.isAlphaPremultiplied = !(cm.isAlphaPremultiplied);
- return cm;
- }
+ SampleModel sm;
+ if (pixel_bits == 1 || pixel_bits == 2 || pixel_bits == 4)
+ sm = new MultiPixelPackedSampleModel(transferType, w, h, pixel_bits);
+ else
+ sm = new ComponentSampleModel(transferType, w, h, 1, w, new int[]{0});
+ return sm;
+ }
+
+ /**
+ * Sets the transparent pixel. This is called by the various constructors.
+ *
+ * @param t the transparent pixel
+ */
+ private void setTransparentPixel(int t)
+ {
+ if (t >= 0 && t < map_size)
+ {
+ rgb[t] &= 0xffffff; // Make the value transparent.
+ trans = t;
+ if (transparency == OPAQUE)
+ {
+ transparency = BITMASK;
+ hasAlpha = true;
+ }
+ }
+ }
}
diff --git a/java/awt/image/RGBImageFilter.java b/java/awt/image/RGBImageFilter.java
index ecfed0674..c777fecd9 100644
--- a/java/awt/image/RGBImageFilter.java
+++ b/java/awt/image/RGBImageFilter.java
@@ -46,228 +46,220 @@ package java.awt.image;
*/
public abstract class RGBImageFilter extends ImageFilter
{
- protected ColorModel origmodel;
+ protected ColorModel origmodel;
- protected ColorModel newmodel;
+ protected ColorModel newmodel;
- /**
- Specifies whether to apply the filter to the index entries of the
- IndexColorModel. Subclasses should set this to true if the filter
- does not depend on the pixel's coordinate.
- */
- protected boolean canFilterIndexColorModel = false;
+ /**
+ * Specifies whether to apply the filter to the index entries of the
+ * IndexColorModel. Subclasses should set this to true if the filter
+ * does not depend on the pixel's coordinate.
+ */
+ protected boolean canFilterIndexColorModel = false;
- /**
- Construct new RGBImageFilter.
- */
- public RGBImageFilter()
- {
- }
+ /**
+ * Construct new RGBImageFilter.
+ */
+ public RGBImageFilter()
+ {
+ }
- /**
- * Sets the ColorModel used to filter with. If the specified ColorModel is IndexColorModel
- * and canFilterIndexColorModel is true, we subsitute the ColorModel for a filtered one
- * here and in setPixels whenever the original one appears. Otherwise overrides the default
- * ColorModel of ImageProducer and specifies the default RGBColorModel
- *
- * @param model the color model to be used most often by setPixels
- * @see ColorModel */
- public void setColorModel(ColorModel model)
- {
- origmodel = model;
- newmodel = model;
+ /**
+ * Sets the ColorModel used to filter with. If the specified ColorModel is
+ * IndexColorModel and canFilterIndexColorModel is true, we subsitute the
+ * ColorModel for a filtered one here and in setPixels whenever the original
+ * one appears. Otherwise overrides the default ColorModel of ImageProducer
+ * and specifies the default RGBColorModel
+ *
+ * @param model the color model to be used most often by setPixels
+ *
+ * @see ColorModel
+ */
+ public void setColorModel(ColorModel model)
+ {
+ if ((model instanceof IndexColorModel) && canFilterIndexColorModel)
+ {
+ ColorModel newCM = filterIndexColorModel((IndexColorModel) model);
+ substituteColorModel(model, newCM);
+ consumer.setColorModel(newmodel);
+ }
+ else
+ {
+ consumer.setColorModel(ColorModel.getRGBdefault());
+ }
+ }
- if( ( model instanceof IndexColorModel) && canFilterIndexColorModel ) {
- newmodel = filterIndexColorModel( (IndexColorModel) model );
- if (consumer != null)
- consumer.setColorModel(newmodel);
- }
- else {
- if (consumer != null)
- consumer.setColorModel(ColorModel.getRGBdefault());
- }
- }
-
- /**
- Registers a new ColorModel to subsitute for the old ColorModel when
- setPixels encounters the a pixel with the old ColorModel. The pixel
- remains unchanged except for a new ColorModel.
-
- @param oldcm the old ColorModel
- @param newcm the new ColorModel
- */
- public void substituteColorModel(ColorModel oldcm,
- ColorModel newcm)
- {
- origmodel = oldcm;
- newmodel = newcm;
- }
-
- /**
- Filters an IndexColorModel through the filterRGB function. Uses
- coordinates of -1 to indicate its filtering an index and not a pixel.
-
- @param icm an IndexColorModel to filter
- */
- public IndexColorModel filterIndexColorModel(IndexColorModel icm)
- {
- int len = icm.getMapSize(), rgb;
- byte reds[] = new byte[len], greens[] = new byte[len], blues[] = new byte[len], alphas[] = new byte[len];
-
- icm.getAlphas( alphas );
- icm.getReds( reds );
- icm.getGreens( greens );
- icm.getBlues( blues );
-
- for( int i = 0; i < len; i++ )
- {
- rgb = filterRGB( -1, -1, makeColor ( alphas[i], reds[i], greens[i], blues[i] ) );
- alphas[i] = (byte)(( 0xff000000 & rgb ) >> 24);
- reds[i] = (byte)(( 0xff0000 & rgb ) >> 16);
- greens[i] = (byte)(( 0xff00 & rgb ) >> 8);
- blues[i] = (byte)(0xff & rgb);
- }
- return new IndexColorModel( icm.getPixelSize(), len, reds, greens, blues, alphas );
- }
-
- private int makeColor( byte a, byte r, byte g, byte b )
- {
- return ( 0xff000000 & (a << 24) | 0xff0000 & (r << 16) | 0xff00 & (g << 8) | 0xff & b );
- }
-
- /**
- This functions filters a set of RGB pixels through filterRGB.
-
- @param x the x coordinate of the rectangle
- @param y the y coordinate of the rectangle
- @param w the width of the rectangle
- @param h the height of the rectangle
- @param pixels the array of pixel values
- @param offset the index of the first pixels in the <code>pixels</code> array
- @param scansize the width to use in extracting pixels from the <code>pixels</code> array
- */
- public void filterRGBPixels(int x, int y, int w, int h, int[] pixels,
- int offset, int scansize)
- {
- for (int yp = 0; yp < h; yp++)
- {
- for (int xp = 0; xp < w; xp++)
- {
- pixels[offset + xp] = filterRGB(xp + x, yp + y, pixels[offset + xp]);
- }
- offset += scansize;
- }
- }
-
-
- /**
- * If the ColorModel is the same ColorModel which as already converted
- * then it converts it the converted ColorModel. Otherwise it passes the
- * array of pixels through filterRGBpixels.
- *
- * @param x the x coordinate of the rectangle
- * @param y the y coordinate of the rectangle
- * @param w the width of the rectangle
- * @param h the height of the rectangle
- * @param model the <code>ColorModel</code> used to translate the pixels
- * @param pixels the array of pixel values
- * @param offset the index of the first pixels in the <code>pixels</code> array
- * @param scansize the width to use in extracting pixels from the <code>pixels</code> array
- */
- public void setPixels(int x, int y, int w, int h,
- ColorModel model, byte[] pixels,
- int offset, int scansize)
- {
- if(model == origmodel && (model instanceof IndexColorModel) && canFilterIndexColorModel)
- {
- if (consumer != null)
- consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
- }
- else
- {
- int intPixels[] =
- convertColorModelToDefault( x, y, w, h, model, pixels, offset, scansize );
- filterRGBPixels( x, y, w, h, intPixels, offset, scansize );
- if (consumer != null)
- consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), intPixels, offset, scansize);
- }
- }
-
- /**
- * This function delivers a rectangle of pixels where any
- * pixel(m,n) is stored in the array as an <code>int</code> at
- * index (n * scansize + m + offset).
- *
- * @param x the x coordinate of the rectangle
- * @param y the y coordinate of the rectangle
- * @param w the width of the rectangle
- * @param h the height of the rectangle
- * @param model the <code>ColorModel</code> used to translate the pixels
- * @param pixels the array of pixel values
- * @param offset the index of the first pixels in the <code>pixels</code> array
- * @param scansize the width to use in extracting pixels from the <code>pixels</code> array
- */
- public void setPixels(int x, int y, int w, int h,
- ColorModel model, int[] pixels,
- int offset, int scansize)
- {
- if(model == origmodel && (model instanceof IndexColorModel) && canFilterIndexColorModel)
- {
- if (consumer != null)
- consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
- }
- else
- {
- //FIXME: Store the filtered pixels in a separate temporary buffer?
- convertColorModelToDefault( x, y, w, h, model, pixels, offset, scansize );
- filterRGBPixels( x, y, w, h, pixels, offset, scansize );
- if (consumer != null)
- consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), pixels, offset, scansize);
- }
- }
-
- private int[] convertColorModelToDefault(int x, int y, int w, int h,
- ColorModel model, byte pixels[],
- int offset, int scansize)
- {
- int intPixels[] = new int[pixels.length];
- for (int i = 0; i < pixels.length; i++)
- intPixels[i] = makeColorbyDefaultCM(model, pixels[i]);
- return intPixels;
- }
+ /**
+ * Registers a new ColorModel to subsitute for the old ColorModel when
+ * setPixels encounters the a pixel with the old ColorModel. The pixel
+ * remains unchanged except for a new ColorModel.
+ *
+ * @param oldcm the old ColorModel
+ * @param newcm the new ColorModel
+ */
+ public void substituteColorModel(ColorModel oldcm, ColorModel newcm)
+ {
+ origmodel = oldcm;
+ newmodel = newcm;
+ }
- private void convertColorModelToDefault(int x, int y, int w, int h,
- ColorModel model, int pixels[],
- int offset, int scansize)
- {
- for (int i = 0; i < pixels.length; i++)
- pixels[i] = makeColorbyDefaultCM(model, pixels[i]);
- }
+ /**
+ * Filters an IndexColorModel through the filterRGB function. Uses
+ * coordinates of -1 to indicate its filtering an index and not a pixel.
+ *
+ * @param icm an IndexColorModel to filter
+ */
+ public IndexColorModel filterIndexColorModel(IndexColorModel icm)
+ {
+ int len = icm.getMapSize();
+ byte[] reds = new byte[len];
+ byte[] greens = new byte[len];
+ byte[] blues = new byte[len];
+ byte[] alphas = new byte[len];
- private int makeColorbyDefaultCM(ColorModel model, byte rgb)
- {
- return makeColor( model.getAlpha( rgb ) * 4, model.getRed( rgb ) * 4, model.getGreen( rgb ) * 4, model.getBlue( rgb ) * 4 );
- }
+ icm.getAlphas( alphas );
+ icm.getReds( reds );
+ icm.getGreens( greens );
+ icm.getBlues( blues );
- private int makeColorbyDefaultCM(ColorModel model, int rgb)
- {
- return makeColor( model.getAlpha( rgb ), model.getRed( rgb ), model.getGreen( rgb ), model.getBlue( rgb ) );
- }
+ int transparent = icm.getTransparentPixel();
+ boolean needAlpha = false;
+ for( int i = 0; i < len; i++ )
+ {
+ int rgb = filterRGB(-1, -1, icm.getRGB(i));
+ alphas[i] = (byte) (rgb >> 24);
+ if (alphas[i] != ((byte) 0xff) && i != transparent)
+ needAlpha = true;
+ reds[i] = (byte) (rgb >> 16);
+ greens[i] = (byte) (rgb >> 8);
+ blues[i] = (byte) (rgb);
+ }
+ IndexColorModel newIcm;
+ if (needAlpha)
+ newIcm = new IndexColorModel(icm.getPixelSize(), len, reds, greens,
+ blues, alphas);
+ else
+ newIcm = new IndexColorModel(icm.getPixelSize(), len, reds, greens,
+ blues, transparent);
+ return newIcm;
+ }
- private int makeColor( int a, int r, int g, int b )
- {
- return (int)( 0xff000000 & (a << 24) | 0xff0000 & (r << 16) | 0xff00 & (g << 8) | 0xff & b );
- }
+ /**
+ * This functions filters a set of RGB pixels through filterRGB.
+ *
+ * @param x the x coordinate of the rectangle
+ * @param y the y coordinate of the rectangle
+ * @param w the width of the rectangle
+ * @param h the height of the rectangle
+ * @param pixels the array of pixel values
+ * @param offset the index of the first pixels in the
+ * <code>pixels</code> array
+ * @param scansize the width to use in extracting pixels from the
+ * <code>pixels</code> array
+ */
+ public void filterRGBPixels(int x, int y, int w, int h, int[] pixels,
+ int offset, int scansize)
+ {
+ int index = offset;
+ for (int yp = 0; yp < h; yp++)
+ {
+ for (int xp = 0; xp < w; xp++)
+ {
+ pixels[index] = filterRGB(xp + x, yp + y, pixels[index]);
+ index++;
+ }
+ index += scansize - w;
+ }
+ consumer.setPixels(x, y, w, h, ColorModel.getRGBdefault(), pixels, offset,
+ scansize);
+ }
+ /**
+ * If the ColorModel is the same ColorModel which as already converted
+ * then it converts it the converted ColorModel. Otherwise it passes the
+ * array of pixels through filterRGBpixels.
+ *
+ * @param x the x coordinate of the rectangle
+ * @param y the y coordinate of the rectangle
+ * @param w the width of the rectangle
+ * @param h the height of the rectangle
+ * @param model the <code>ColorModel</code> used to translate the pixels
+ * @param pixels the array of pixel values
+ * @param offset the index of the first pixels in the <code>pixels</code>
+ * array
+ * @param scansize the width to use in extracting pixels from the
+ * <code>pixels</code> array
+ */
+ public void setPixels(int x, int y, int w, int h, ColorModel model,
+ byte[] pixels, int offset, int scansize)
+ {
+ if (model == origmodel)
+ {
+ consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
+ }
+ else
+ {
+ int[] filtered = new int[w];
+ int index = offset;
+ for (int yp = 0; yp < h; yp++)
+ {
+ for (int xp = 0; xp < w; xp++)
+ {
+ filtered[xp] = model.getRGB((pixels[index] & 0xff));
+ index++;
+ }
+ index += scansize - w;
+ filterRGBPixels(x, y + yp, w, 1, filtered, 0, w);
+ }
+ }
+ }
- /**
- Filters a single pixel from the default ColorModel.
+ /**
+ * This function delivers a rectangle of pixels where any
+ * pixel(m,n) is stored in the array as an <code>int</code> at
+ * index (n * scansize + m + offset).
+ *
+ * @param x the x coordinate of the rectangle
+ * @param y the y coordinate of the rectangle
+ * @param w the width of the rectangle
+ * @param h the height of the rectangle
+ * @param model the <code>ColorModel</code> used to translate the pixels
+ * @param pixels the array of pixel values
+ * @param offset the index of the first pixels in the <code>pixels</code>
+ * array
+ * @param scansize the width to use in extracting pixels from the
+ * <code>pixels</code> array
+ */
+ public void setPixels(int x, int y, int w, int h, ColorModel model,
+ int[] pixels, int offset, int scansize)
+ {
+ if (model == origmodel)
+ {
+ consumer.setPixels(x, y, w, h, newmodel, pixels, offset, scansize);
+ }
+ else
+ {
+ int[] filtered = new int[w];
+ int index = offset;
+ for (int yp = 0; yp < h; yp++)
+ {
+ for (int xp = 0; xp < w; xp++)
+ {
+ filtered[xp] = model.getRGB((pixels[index] & 0xff));
+ index++;
+ }
+ index += scansize - w;
+ filterRGBPixels(x, y + yp, w, 1, filtered, 0, w);
+ }
+ }
+ }
- @param x x-coordinate
- @param y y-coordinate
- @param rgb color
- */
- public abstract int filterRGB(int x,
- int y,
- int rgb);
+ /**
+ * Filters a single pixel from the default ColorModel.
+ *
+ * @param x x-coordinate
+ * @param y y-coordinate
+ * @param rgb color
+ */
+ public abstract int filterRGB(int x, int y, int rgb);
}
diff --git a/java/awt/image/ReplicateScaleFilter.java b/java/awt/image/ReplicateScaleFilter.java
index 933527b43..5ba03f182 100644
--- a/java/awt/image/ReplicateScaleFilter.java
+++ b/java/awt/image/ReplicateScaleFilter.java
@@ -46,6 +46,7 @@ import java.util.Hashtable;
* exact method is not defined by Sun but some sort of fast Box filter should
* probably be correct.
* <br>
+ * Currently this filter does nothing and needs to be implemented.
*
* @author C. Brian Jones (cbj@gnu.org)
*/
@@ -116,11 +117,11 @@ public class ReplicateScaleFilter extends ImageFilter
}
else if (destWidth < 0)
{
- destWidth = (int) (width * ((double) destHeight / srcHeight));
+ destWidth = width * destHeight / srcHeight;
}
else if (destHeight < 0)
{
- destHeight = (int) (height * ((double) destWidth / srcWidth));
+ destHeight = height * destWidth / srcWidth;
}
if (consumer != null)
@@ -158,19 +159,35 @@ public class ReplicateScaleFilter extends ImageFilter
public void setPixels(int x, int y, int w, int h,
ColorModel model, byte[] pixels, int offset, int scansize)
{
- double rx = ((double) srcWidth) / destWidth;
- double ry = ((double) srcHeight) / destHeight;
-
- int destScansize = (int) Math.round(scansize / rx);
-
- byte[] destPixels = replicatePixels(x, y, w, h,
- model, pixels, offset, scansize,
- rx, ry, destScansize);
-
- if (consumer != null)
- consumer.setPixels((int) Math.floor(x/rx), (int) Math.floor(y/ry),
- (int) Math.ceil(w/rx), (int) Math.ceil(h/ry),
- model, destPixels, 0, destScansize);
+ if (srcrows == null || srccols == null)
+ setupSources();
+ int dx1 = (2 * x * destWidth + srcWidth - 1) / (2 * destWidth);
+ int dy1 = (2 * y * destHeight + srcHeight - 1) / (2 * destHeight);
+ byte[] pix;
+ if (outpixbuf != null && outpixbuf instanceof byte[])
+ {
+ pix = (byte[]) outpixbuf;
+ }
+ else
+ {
+ pix = new byte[destWidth];
+ outpixbuf = pix;
+ }
+ int sy, sx;
+ for (int yy = dy1; (sy = srcrows[yy]) < y + h; yy++)
+ {
+ int offs = offset + scansize * (sy - y);
+ int xx;
+ for (xx = dx1; (sx = srccols[xx]) < x + w; xx++)
+ {
+ pix[xx] = pixels[offs + sx - x];
+ }
+ if (xx > dx1)
+ {
+ consumer.setPixels(dx1, yy, xx - dx1, 1, model, pix, dx1,
+ destWidth);
+ }
+ }
}
/**
@@ -190,59 +207,52 @@ public class ReplicateScaleFilter extends ImageFilter
public void setPixels(int x, int y, int w, int h,
ColorModel model, int[] pixels, int offset, int scansize)
{
- double rx = ((double) srcWidth) / destWidth;
- double ry = ((double) srcHeight) / destHeight;
-
- int destScansize = (int) Math.round(scansize / rx);
-
- int[] destPixels = replicatePixels(x, y, w, h,
- model, pixels, offset, scansize,
- rx, ry, destScansize);
-
- if (consumer != null)
- consumer.setPixels((int) Math.floor(x/rx), (int) Math.floor(y/ry),
- (int) Math.ceil(w/rx), (int) Math.ceil(h/ry),
- model, destPixels, 0, destScansize);
- }
-
- private byte[] replicatePixels(int srcx, int srcy, int srcw, int srch,
- ColorModel model, byte[] srcPixels,
- int srcOffset, int srcScansize,
- double rx, double ry, int destScansize)
- {
- byte[] destPixels =
- new byte[(int) Math.ceil(srcw/rx) * (int) Math.ceil(srch/ry)];
-
- int a, b;
- for (int i = 0; i < destPixels.length; i++)
- {
- a = (int) ((int) ( ((double) i) / destScansize) * ry) * srcScansize;
- b = (int) ((i % destScansize) * rx);
- if ((a + b + srcOffset) < srcPixels.length)
- destPixels[i] = srcPixels[a + b + srcOffset];
- }
-
- return destPixels;
+ if (srcrows == null || srccols == null)
+ setupSources();
+ int dx1 = (2 * x * destWidth + srcWidth - 1) / (2 * destWidth);
+ int dy1 = (2 * y * destHeight + srcHeight - 1) / (2 * destHeight);
+ int[] pix;
+ if (outpixbuf != null && outpixbuf instanceof int[])
+ {
+ pix = (int[]) outpixbuf;
+ }
+ else
+ {
+ pix = new int[destWidth];
+ outpixbuf = pix;
+ }
+ int sy, sx;
+ for (int yy = dy1; (sy = srcrows[yy]) < y + h; yy++)
+ {
+ int offs = offset + scansize * (sy - y);
+ int xx;
+ for (xx = dx1; (sx = srccols[xx]) < x + w; xx++)
+ {
+ pix[xx] = pixels[offs + sx - x];
+ }
+ if (xx > dx1)
+ {
+ consumer.setPixels(dx1, yy, xx - dx1, 1, model, pix, dx1,
+ destWidth);
+ }
+ }
}
- private int[] replicatePixels(int srcx, int srcy, int srcw, int srch,
- ColorModel model, int[] srcPixels,
- int srcOffset, int srcScansize,
- double rx, double ry, int destScansize)
- {
- int[] destPixels =
- new int[(int) Math.ceil(srcw/rx) * (int) Math.ceil(srch/ry)];
-
- int a, b;
- for (int i = 0; i < destPixels.length; i++)
- {
- a = (int) ((int) ( ((double) i) / destScansize) * ry) * srcScansize;
- b = (int) ((i % destScansize) * rx);
- if ((a + b + srcOffset) < srcPixels.length)
- destPixels[i] = srcPixels[a + b + srcOffset];
- }
-
- return destPixels;
- }
+ /**
+ * Sets up the srcrows and srccols arrays.
+ */
+ private void setupSources()
+ {
+ srcrows = new int[destHeight + 1];
+ for (int y = 0; y <= destHeight; y++)
+ {
+ srcrows[y] = (2 * y * srcHeight + srcHeight) / (2 * destHeight);
+ }
+ srccols = new int[destWidth + 1];
+ for (int x = 0; x <= destWidth; x++)
+ {
+ srccols[x] = (2 * x * srcWidth + srcWidth) / (2 * destWidth);
+ }
+ }
}
diff --git a/java/awt/image/SampleModel.java b/java/awt/image/SampleModel.java
index cb352bb4d..506e78a9b 100644
--- a/java/awt/image/SampleModel.java
+++ b/java/awt/image/SampleModel.java
@@ -246,9 +246,7 @@ public abstract class SampleModel
public void setDataElements(int x, int y, int w, int h,
Object obj, DataBuffer data)
{
- int size = w * h;
int numDataElements = getNumDataElements();
- int dataSize = numDataElements * size;
Object pixelData;
switch (getTransferType())
@@ -257,25 +255,34 @@ public abstract class SampleModel
pixelData = new byte[numDataElements];
break;
case DataBuffer.TYPE_USHORT:
+ case DataBuffer.TYPE_SHORT:
pixelData = new short[numDataElements];
break;
case DataBuffer.TYPE_INT:
pixelData = new int[numDataElements];
break;
+ case DataBuffer.TYPE_FLOAT:
+ pixelData = new float[numDataElements];
+ break;
+ case DataBuffer.TYPE_DOUBLE:
+ pixelData = new double[numDataElements];
+ break;
default:
- // Seems like the only sensible thing to do.
- throw new ClassCastException();
+ // The RI silently igores invalid types.
+ pixelData = null;
}
- int inOffset = 0;
- for (int yy = y; yy < (y + h); yy++)
+ int inOffset = 0;
+ if (pixelData != null)
{
- for (int xx = x; xx < (x + w); xx++)
+ for (int yy=y; yy<(y+h); yy++)
{
- System.arraycopy(obj, inOffset, pixelData, 0,
- numDataElements);
- setDataElements(xx, yy, pixelData, data);
- inOffset += numDataElements;
+ for (int xx=x; xx<(x+w); xx++)
+ {
+ System.arraycopy(obj, inOffset, pixelData, 0, numDataElements);
+ setDataElements(xx, yy, pixelData, data);
+ inOffset += numDataElements;
+ }
}
}
}
diff --git a/java/awt/image/SinglePixelPackedSampleModel.java b/java/awt/image/SinglePixelPackedSampleModel.java
index a37fc0bba..9ed948c54 100644
--- a/java/awt/image/SinglePixelPackedSampleModel.java
+++ b/java/awt/image/SinglePixelPackedSampleModel.java
@@ -412,110 +412,31 @@ public class SinglePixelPackedSampleModel extends SampleModel
return (samples & bitMasks[b]) >>> bitOffsets[b];
}
- /**
- * This method implements a more efficient way to set data elements than the
- * default implementation of the super class. It sets the data elements line
- * by line instead of pixel by pixel.
- *
- * @param x The x-coordinate of the data elements in <code>obj</code>.
- * @param y The y-coordinate of the data elements in <code>obj</code>.
- * @param w The width of the data elements in <code>obj</code>.
- * @param h The height of the data elements in <code>obj</code>.
- * @param obj The primitive array containing the data elements to set.
- * @param data The DataBuffer to store the data elements into.
- * @see java.awt.image.SampleModel#setDataElements(int, int, int, int,
- * java.lang.Object, java.awt.image.DataBuffer)
- */
- public void setDataElements(int x, int y, int w, int h,
- Object obj, DataBuffer data)
- {
-
- Object pixelData;
- switch (getTransferType())
- {
- case DataBuffer.TYPE_BYTE:
- pixelData = ((DataBufferByte) data).getData();
- break;
- case DataBuffer.TYPE_USHORT:
- pixelData = ((DataBufferUShort) data).getData();
- break;
- case DataBuffer.TYPE_INT:
- pixelData = ((DataBufferInt) data).getData();
- break;
- default:
- // Seems like the only sensible thing to do.
- throw new ClassCastException();
- }
-
- int inOffset = 0;
- int dataOffset = scanlineStride*y + x + data.getOffset();
- for (int yy=y; yy<(y+h); yy++)
- {
- System.arraycopy(obj,inOffset,pixelData,dataOffset,w);
- dataOffset += scanlineStride;
- inOffset += w;
- }
- }
-
-
public void setDataElements(int x, int y, Object obj, DataBuffer data)
{
- int offset = scanlineStride*y + x + data.getOffset();
int transferType = getTransferType();
- if (getTransferType() != data.getDataType())
- {
- throw new IllegalArgumentException("transfer type ("+
- getTransferType()+"), "+
- "does not match data "+
- "buffer type (" +
- data.getDataType() +
- ").");
- }
-
- try
- {
- switch (transferType)
- {
- case DataBuffer.TYPE_BYTE:
- {
- DataBufferByte out = (DataBufferByte) data;
- byte[] in = (byte[]) obj;
- out.getData()[offset] = in[0];
- return;
- }
- case DataBuffer.TYPE_USHORT:
- {
- DataBufferUShort out = (DataBufferUShort) data;
- short[] in = (short[]) obj;
- out.getData()[offset] = in[0];
- return;
- }
- case DataBuffer.TYPE_INT:
- {
- DataBufferInt out = (DataBufferInt) data;
- int[] in = (int[]) obj;
- out.getData()[offset] = in[0];
- return;
- }
- // FIXME: Fill in the other possible types.
- default:
- throw new InternalError();
- }
- }
- catch (ArrayIndexOutOfBoundsException aioobe)
- {
- String msg = "While writing data elements" +
- ", x="+x+", y="+y+
- ", width="+width+", height="+height+
- ", scanlineStride="+scanlineStride+
- ", offset="+offset+
- ", data.getSize()="+data.getSize()+
- ", data.getOffset()="+data.getOffset()+
- ": " +
- aioobe;
- throw new ArrayIndexOutOfBoundsException(msg);
- }
+ switch (transferType)
+ {
+ case DataBuffer.TYPE_BYTE:
+ {
+ byte[] in = (byte[]) obj;
+ data.setElem(y * scanlineStride + x, ((int) in[0]) & 0xff);
+ break;
+ }
+ case DataBuffer.TYPE_USHORT:
+ {
+ short[] in = (short[]) obj;
+ data.setElem(y * scanlineStride + x, ((int) in[0]) & 0xffff);
+ break;
+ }
+ case DataBuffer.TYPE_INT:
+ {
+ int[] in = (int[]) obj;
+ data.setElem(y * scanlineStride + x, in[0]);
+ break;
+ }
+ }
}
/**
diff --git a/java/awt/image/WritableRaster.java b/java/awt/image/WritableRaster.java
index d2ea4a8aa..bf8db140c 100644
--- a/java/awt/image/WritableRaster.java
+++ b/java/awt/image/WritableRaster.java
@@ -150,6 +150,25 @@ public class WritableRaster extends Raster
sampleModelTranslateY + childMinY - parentY),
this);
}
+
+ public Raster createChild(int parentX, int parentY, int width,
+ int height, int childMinX, int childMinY,
+ int[] bandList)
+ {
+ if (parentX < minX || parentX + width > minX + this.width
+ || parentY < minY || parentY + height > minY + this.height)
+ throw new RasterFormatException("Child raster extends beyond parent");
+
+ SampleModel sm = (bandList == null) ?
+ sampleModel :
+ sampleModel.createSubsetSampleModel(bandList);
+
+ return new WritableRaster(sm, dataBuffer,
+ new Rectangle(childMinX, childMinY, width, height),
+ new Point(sampleModelTranslateX + childMinX - parentX,
+ sampleModelTranslateY + childMinY - parentY),
+ this);
+ }
public void setDataElements(int x, int y, Object inData)
{
diff --git a/java/beans/DesignMode.java b/java/beans/DesignMode.java
index 39805d50c..9897f022e 100644
--- a/java/beans/DesignMode.java
+++ b/java/beans/DesignMode.java
@@ -1,5 +1,5 @@
/* java.beans.DesignMode
- Copyright (C) 1999 Free Software Foundation, Inc.
+ Copyright (C) 1999, 2006, Free Software Foundation, Inc.
This file is part of GNU Classpath.
@@ -39,7 +39,8 @@ exception statement from your version. */
package java.beans;
/**
- * <code>BeanContextChild</code> implementors implement this to get information about whether they are in a design time or runtime environment.
+ * <code>BeanContextChild</code> implementors implement this to get information
+ * about whether they are in a design time or runtime environment.
* The reason this is restricted to <code>BeanContextChild</code>ren is that
* only things in the <code>BeanContext</code> hierarchy are given this
* information in the first place.
@@ -48,46 +49,47 @@ package java.beans;
* @since JDK1.2
* @see java.beans.beancontext.BeanContextChild
*/
+public interface DesignMode
+{
-public interface DesignMode {
- /**
- * Use this name when firing <code>PropertyChangeEvent</code>s from your Bean.
- * @fixme Check whether PROPERTYNAME is set to same value as Sun.
- */
- String PROPERTYNAME = "designTime";
+ /**
+ * Use this name when firing <code>PropertyChangeEvent</code>s from your Bean.
+ */
+ String PROPERTYNAME = "designTime";
- /**
- * The environment will call this method on your
- * <code>BeanContextChild</code> when it is registered in a parent
- * <code>BeanContext</code> or when behavior needs to switch from
- * design time to runtime behavior (or vice versa).
- * <P>
- *
- * <code>BeanContext</code>s are required to fire
- * <code>PropertyChangeEvent</code>s when properties change.
- * <code>designTime</code> is a property, and therefore when you
- * implement <code>setDesignTime()</code>, you need to fire a
- * <code>PropertyChangeEvent</code> with the old value, the new
- * value and using <code>PROPERTYNAME</code> as the property name.
- *
- * @param designTime the new value of design time,
- * <code>true</code> if it is design time,
- * <code>false</code> if it is runtime.
- *
- * @fixme I'm frankly not really sure whether it's the case that
- * the BeanContext can <em>change</em> the status of the Bean from
- * design time to runtime. But it appears that it may be so.
- *
- * @see java.util.PropertyChangeEvent
- * @see java.beans.beancontext.BeanContext
- * @see #PROPERTYNAME
- */
- void setDesignTime(boolean designTime);
+ /**
+ * The environment will call this method on your
+ * <code>BeanContextChild</code> when it is registered in a parent
+ * <code>BeanContext</code> or when behavior needs to switch from
+ * design time to runtime behavior (or vice versa).
+ * <P>
+ *
+ * <code>BeanContext</code>s are required to fire
+ * <code>PropertyChangeEvent</code>s when properties change.
+ * <code>designTime</code> is a property, and therefore when you
+ * implement <code>setDesignTime()</code>, you need to fire a
+ * <code>PropertyChangeEvent</code> with the old value, the new
+ * value and using <code>PROPERTYNAME</code> as the property name.
+ *
+ * @param designTime the new value of design time,
+ * <code>true</code> if it is design time,
+ * <code>false</code> if it is runtime.
+ *
+ * @fixme I'm frankly not really sure whether it's the case that
+ * the BeanContext can <em>change</em> the status of the Bean from
+ * design time to runtime. But it appears that it may be so.
+ *
+ * @see java.beans.PropertyChangeEvent
+ * @see java.beans.beancontext.BeanContext
+ * @see #PROPERTYNAME
+ */
+ void setDesignTime(boolean designTime);
+
+ /**
+ * This method should tell whether it is design time or runtime.
+ * @return <code>true</code> if design time, <code>false</code> if
+ * runtime.
+ */
+ boolean isDesignTime();
- /**
- * This method should tell whether it is design time or runtime.
- * @return <code>true</code> if design time, <code>false</code> if
- * runtime.
- */
- boolean isDesignTime();
}
diff --git a/java/beans/Statement.java b/java/beans/Statement.java
index 62a5ad7b6..0a01798ad 100644
--- a/java/beans/Statement.java
+++ b/java/beans/Statement.java
@@ -1,5 +1,5 @@
/* Statement.java
- Copyright (C) 2004, 2005 Free Software Foundation, Inc.
+ Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
This file is part of GNU Classpath.
@@ -346,16 +346,20 @@ public class Statement
/** Return the statement object. */
public Object getTarget() { return target; }
- /** Return a string representation. */
+ /**
+ * Returns a string representation of this <code>Statement</code>.
+ *
+ * @return A string representation of this <code>Statement</code>.
+ */
public String toString()
{
StringBuffer result = new StringBuffer();
- String targetName = target.getClass().getName();
- if ( targetName.startsWith("java"))
- {
- targetName = targetName.substring(targetName.lastIndexOf('.') + 1);
- }
+ String targetName;
+ if (target != null)
+ targetName = target.getClass().getSimpleName();
+ else
+ targetName = "null";
result.append(targetName);
result.append(".");
@@ -369,10 +373,10 @@ public class Statement
result.append(
( arguments[i] == null ) ? "null" :
( arguments[i] instanceof String ) ? "\"" + arguments[i] + "\"" :
- arguments[i].getClass().getName());
+ arguments[i].getClass().getSimpleName());
sep = ", ";
}
- result.append(")");
+ result.append(");");
return result.toString();
}
diff --git a/java/beans/beancontext/BeanContextServicesSupport.java b/java/beans/beancontext/BeanContextServicesSupport.java
index 4da523eeb..ce09b86cc 100644
--- a/java/beans/beancontext/BeanContextServicesSupport.java
+++ b/java/beans/beancontext/BeanContextServicesSupport.java
@@ -46,12 +46,21 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.TooManyListenersException;
/**
+ * This is a helper class for implementing a bean context which
+ * supplies services. It is intended to be used either by
+ * subclassing or by calling methods of this implementation
+ * from another.
+ *
* @author Michael Koch
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
* @since 1.2
*/
public class BeanContextServicesSupport
@@ -119,51 +128,233 @@ public class BeanContextServicesSupport
protected BeanContextServiceProvider serviceProvider;
- private BCSSServiceProvider()
+ private Class serviceClass;
+
+ private BCSSServiceProvider(Class serviceClass,
+ BeanContextServiceProvider provider)
{
+ this.serviceClass = serviceClass;
+ serviceProvider = provider;
}
protected BeanContextServiceProvider getServiceProvider()
{
return serviceProvider;
}
+
+ private Class getServiceClass()
+ {
+ return serviceClass;
+ }
+
}
- protected transient ArrayList bcsListeners;
+ /**
+ * Represents a request for a service. This is
+ * a common superclass used by the classes which maintain
+ * the listener-requestor and service-requestor relationships.
+ *
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ */
+ private static abstract class Request
+ {
+ private Object requestor;
+
+ public Request(Object requestor)
+ {
+ this.requestor = requestor;
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof Request)
+ {
+ Request req = (Request) obj;
+ return req.getRequestor().equals(requestor);
+ }
+ return false;
+ }
+
+ public Object getRequestor()
+ {
+ return requestor;
+ }
+
+ }
+
+ /**
+ * Represents a relationship between a service requestor
+ * and a revocation listener.
+ *
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ */
+ private static class ServiceRequest
+ extends Request
+ {
+
+ private BeanContextServiceRevokedListener listener;
+ public ServiceRequest(Object requestor,
+ BeanContextServiceRevokedListener listener)
+ {
+ super(requestor);
+ this.listener = listener;
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof ServiceRequest)
+ {
+ ServiceRequest sr = (ServiceRequest) obj;
+ return (super.equals(obj) &&
+ sr.getListener().equals(listener));
+ }
+ return false;
+ }
+
+ public BeanContextServiceRevokedListener getListener()
+ {
+ return listener;
+ }
+ }
+
+ /**
+ * Represents a relationship between a service requestor
+ * and a service instance.
+ *
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ */
+ private static class ServiceLease
+ extends Request
+ {
+
+ private Object service;
+
+ public ServiceLease(Object requestor, Object service)
+ {
+ super(requestor);
+ this.service = service;
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof ServiceLease)
+ {
+ ServiceLease sl = (ServiceLease) obj;
+ return (super.equals(obj) &&
+ sl.getService().equals(service));
+ }
+ return false;
+ }
+
+ public Object getService()
+ {
+ return service;
+ }
+ }
+
+ /**
+ * A collection of listeners who receive availability
+ * and revocation notifications.
+ */
+ protected transient ArrayList bcsListeners;
+
protected transient BCSSProxyServiceProvider proxy;
+ /**
+ * The number of serializable service providers.
+ */
protected transient int serializable;
+ /**
+ * A map of registered services, linking the service
+ * class to its associated {@link BCSSServiceProvider}.
+ */
protected transient HashMap services;
+ /**
+ * A map of children to a list of services they
+ * have obtained.
+ */
+ private transient HashMap serviceUsers;
+
+ /**
+ * A map of services to {@link ServiceRequest}s.
+ */
+ private transient HashMap serviceRequests;
+
+ /**
+ * A map of {@link ServiceLease}s to providers.
+ */
+ private transient HashMap serviceLeases;
+
+ /**
+ * Construct a {@link BeanContextServicesSupport} instance.
+ */
public BeanContextServicesSupport ()
{
super();
}
+ /**
+ * Construct a {@link BeanContextServicesSupport} instance.
+ *
+ * @param peer the bean context services peer (<code>null</code> permitted).
+ */
public BeanContextServicesSupport (BeanContextServices peer)
{
super(peer);
}
+ /**
+ * Construct a {@link BeanContextServicesSupport} instance.
+ *
+ * @param peer the bean context peer (<code>null</code> permitted).
+ * @param locale the locale (<code>null</code> permitted, equivalent to
+ * the default locale).
+ */
public BeanContextServicesSupport(BeanContextServices peer, Locale locale)
{
super(peer, locale);
}
+ /**
+ * Construct a {@link BeanContextServicesSupport} instance.
+ *
+ * @param peer the bean context peer (<code>null</code> permitted).
+ * @param locale the locale (<code>null</code> permitted, equivalent to
+ * the default locale).
+ * @param dtime a flag indicating whether or not the bean context is in
+ * design time mode.
+ */
public BeanContextServicesSupport(BeanContextServices peer, Locale locale,
boolean dtime)
{
super(peer, locale, dtime);
}
+ /**
+ * Construct a {@link BeanContextServicesSupport} instance.
+ *
+ * @param peer the bean context peer (<code>null</code> permitted).
+ * @param locale the locale (<code>null</code> permitted, equivalent to
+ * the default locale).
+ * @param dtime a flag indicating whether or not the bean context is in
+ * design time mode.
+ * @param visible initial value of the <code>okToUseGui</code> flag.
+ */
public BeanContextServicesSupport(BeanContextServices peer, Locale locale,
boolean dtime, boolean visible)
{
super(peer, locale, dtime, visible);
}
-
+
+ /**
+ * Adds a new listener for service availability and
+ * revocation events.
+ *
+ * @param listener the listener to add.
+ */
public void addBeanContextServicesListener
(BeanContextServicesListener listener)
{
@@ -174,60 +365,168 @@ public class BeanContextServicesSupport
}
}
+ /**
+ * Registers a new service from the specified service provider.
+ * The service is internally associated with the service provider
+ * and a <code>BeanContextServiceAvailableEvent</code> is fired. If
+ * the service is already registered, then this method instead
+ * returns <code>false</code>. This is equivalent to calling
+ * <code>addService(serviceClass, bcsp, true)</code>.
+ *
+ * @param serviceClass the class of the service to be registered.
+ * @param bcsp the provider of the given service.
+ * @return true if the service was registered successfully.
+ * @see #addService(Class, BeanContextServiceProvider, boolean)
+ */
public boolean addService (Class serviceClass,
BeanContextServiceProvider bcsp)
{
return addService(serviceClass, bcsp, true);
}
+ /**
+ * Registers a new service from the specified service provider.
+ * The service is internally associated with the service provider
+ * and (if <code>fireEvent</code> is true) a
+ * <code>BeanContextServiceAvailableEvent</code> is fired. If
+ * the service is already registered, then this method instead
+ * returns <code>false</code>.
+ *
+ * @param serviceClass the class of the service to be registered.
+ * @param bcsp the provider of the given service.
+ * @param fireEvent true if a service availability event should
+ * be fired.
+ * @return true if the service was registered successfully.
+ */
protected boolean addService (Class serviceClass,
BeanContextServiceProvider bcsp,
boolean fireEvent)
{
- synchronized (services)
+ synchronized (globalHierarchyLock)
{
- if (services.containsKey(serviceClass))
- return false;
- services.put(serviceClass, bcsp);
- if (bcsp instanceof Serializable)
- ++serializable;
- fireServiceAdded(serviceClass);
- return true;
+ synchronized (services)
+ {
+ if (services.containsKey(serviceClass))
+ return false;
+ services.put(serviceClass,
+ createBCSSServiceProvider(serviceClass, bcsp));
+ if (bcsp instanceof Serializable)
+ ++serializable;
+ if (fireEvent)
+ fireServiceAdded(serviceClass);
+ return true;
+ }
}
}
+ /**
+ * Deserializes any service providers which are serializable. This
+ * method is called by the <code>readObject</code> method of
+ * {@link BeanContextSupport} prior to deserialization of the children.
+ * Subclasses may envelope its behaviour in order to read further
+ * serialized data to the stream.
+ *
+ * @param oos the stream from which data is being deserialized.
+ * @throws IOException if an I/O error occurs.
+ * @throws ClassNotFoundException if the class of a deserialized object
+ * can not be found.
+ */
protected void bcsPreDeserializationHook (ObjectInputStream ois)
- throws ClassNotFoundException, IOException, NotImplementedException
+ throws ClassNotFoundException, IOException
{
- throw new Error ("Not implemented");
+ serializable = ois.readInt();
+ for (int a = 0; a < serializable; ++a)
+ {
+ BCSSServiceProvider bcsssp = (BCSSServiceProvider) ois.readObject();
+ addService(bcsssp.getServiceClass(), bcsssp.getServiceProvider());
+ }
}
+ /**
+ * Serializes any service providers which are serializable. This
+ * method is called by the <code>writeObject</code> method of
+ * {@link BeanContextSupport} prior to serialization of the children.
+ * Subclasses may envelope its behaviour in order to add further
+ * serialized data to the stream.
+ *
+ * @param oos the stream to which data is being serialized.
+ * @throws IOException if an I/O error occurs.
+ */
protected void bcsPreSerializationHook (ObjectOutputStream oos)
- throws IOException, NotImplementedException
+ throws IOException
{
- throw new Error ("Not implemented");
+ oos.writeInt(serializable);
+ synchronized (services)
+ {
+ Iterator i = services.values().iterator();
+ while (i.hasNext())
+ {
+ BCSSServiceProvider bcsssp = (BCSSServiceProvider) i.next();
+ if (bcsssp.getServiceProvider() instanceof Serializable)
+ oos.writeObject(bcsssp);
+ }
+ }
}
-
+
+ /**
+ * Revokes any services used by a child that has just been removed.
+ * The superclass ({@link BeanContextSupport}) calls this method
+ * when a child has just been successfully removed. Subclasses can
+ * extend this method in order to perform additional operations
+ * on child removal.
+ *
+ * @param child the child being removed.
+ * @param bcsc the support object for the child.
+ */
protected void childJustRemovedHook (Object child,
BeanContextSupport.BCSChild bcsc)
- throws NotImplementedException
{
- throw new Error ("Not implemented");
+ if (child instanceof BeanContextChild)
+ {
+ BeanContextChild bcchild = (BeanContextChild) child;
+ Iterator childServices = ((List) serviceUsers.get(bcchild)).iterator();
+ while (childServices.hasNext())
+ releaseService(bcchild, this, childServices.next());
+ serviceUsers.remove(bcchild);
+ }
}
+ /**
+ * Overrides the {@link BeanContextSupport#createBCSChild} method
+ * so as to use a {@link BCSSChild} instead.
+ *
+ * @param targetChild the child to create the child for.
+ * @param peer the peer which relates to the child if a proxy is used.
+ * @return a new instance of {@link BCSSChild}.
+ */
protected BeanContextSupport.BCSChild createBCSChild (Object targetChild,
Object peer)
{
return new BCSSChild(targetChild, peer);
}
+ /**
+ * Provides a hook so that subclasses can replace the
+ * {@link BCSSServiceProvider} class, used to store registered
+ * service providers, with a subclass without replacing the
+ * {@link #addService(Class, BeanContextServiceProvider)} method.
+ *
+ * @param sc the class of service being registered.
+ * @param bcsp the provider of the service.
+ * @return a instance of {@link BCSSServiceProvider} wrapping the provider.
+ */
protected BeanContextServicesSupport.BCSSServiceProvider
createBCSSServiceProvider (Class sc, BeanContextServiceProvider bcsp)
- throws NotImplementedException
{
- throw new Error ("Not implemented");
+ return new BCSSServiceProvider(sc, bcsp);
}
+ /**
+ * Sends a <code>BeanContextServiceAvailableEvent</code> to all
+ * registered listeners.
+ *
+ * @param bcssae the event to send.
+ */
protected final void fireServiceAdded (BeanContextServiceAvailableEvent bcssae)
{
synchronized (bcsListeners)
@@ -242,12 +541,25 @@ public class BeanContextServicesSupport
}
}
+ /**
+ * Sends a <code>BeanContextServiceAvailableEvent</code> to all
+ * registered listeners.
+ *
+ * @param serviceClass the service that is now available.
+ * @see #fireServiceAdded(BeanContextServiceAvailableEvent)
+ */
protected final void fireServiceAdded (Class serviceClass)
{
fireServiceAdded(new BeanContextServiceAvailableEvent(this,
serviceClass));
}
+ /**
+ * Sends a <code>BeanContextServiceRevokedEvent</code> to all
+ * registered listeners.
+ *
+ * @param event the event to send.
+ */
protected final void fireServiceRevoked(BeanContextServiceRevokedEvent event)
{
synchronized (bcsListeners)
@@ -259,9 +571,26 @@ public class BeanContextServicesSupport
= (BeanContextServicesListener) bcsListeners.get(i);
bcsl.serviceRevoked(event);
}
+ List requests = (List) serviceRequests.get(event.getServiceClass());
+ if (requests != null)
+ {
+ Iterator i = requests.iterator();
+ while (i.hasNext())
+ {
+ ServiceRequest r = (ServiceRequest) i.next();
+ r.getListener().serviceRevoked(event);
+ }
+ }
}
}
+ /**
+ * Sends a <code>BeanContextServiceRevokedEvent</code> to all
+ * registered listeners.
+ *
+ * @param serviceClass the service that has been revoked.
+ * @see #fireServiceRevoked(BeanContextServiceRevokedEvent)
+ */
protected final void fireServiceRevoked (Class serviceClass,
boolean revokeNow)
{
@@ -269,51 +598,166 @@ public class BeanContextServicesSupport
revokeNow));
}
+ /**
+ * Returns the services peer given at construction time,
+ * or <code>null</code> if no peer was given.
+ *
+ * @return the {@link BeanContextServices} peer.
+ */
public BeanContextServices getBeanContextServicesPeer ()
- throws NotImplementedException
{
- throw new Error ("Not implemented");
+ return (BeanContextServices) beanContextChildPeer;
}
+ /**
+ * Returns <code>child</code> as an instance of
+ * {@link BeanContextServicesListener}, or <code>null</code> if
+ * <code>child</code> does not implement that interface.
+ *
+ * @param child the child (<code>null</code> permitted).
+ *
+ * @return The child cast to {@link BeanContextServicesListener}.
+ */
protected static final BeanContextServicesListener
- getChildBeanContextServicesListener (Object child)
- throws NotImplementedException
+ getChildBeanContextServicesListener(Object child)
{
- throw new Error ("Not implemented");
+ if (child instanceof BeanContextServicesListener)
+ return (BeanContextServicesListener) child;
+ else
+ return null;
}
+ /**
+ * Returns an iterator over the currently available
+ * services.
+ *
+ * @return an iterator over the currently available services.
+ */
public Iterator getCurrentServiceClasses ()
{
- synchronized (services)
+ synchronized (globalHierarchyLock)
{
- return services.keySet().iterator();
+ synchronized (services)
+ {
+ return services.keySet().iterator();
+ }
}
}
+ /**
+ * Returns an iterator over the service selectors of the service
+ * provider for the given service. The iterator is actually
+ * obtained by calling the
+ * {@link BeanContextServiceProvider#getCurrentServiceSelectors}
+ * of the provider itself. If the specified service is not available,
+ * <code>null</code> is returned.
+ *
+ * @param serviceClass the service whose provider's selectors should
+ * be iterated over.
+ * @return an {@link Iterator} over the service selectors of the
+ * provider of the given service.
+ */
public Iterator getCurrentServiceSelectors (Class serviceClass)
{
- synchronized (services)
+ synchronized (globalHierarchyLock)
{
- // FIXME: what if service does not exist? Must write a test.
- BeanContextServiceProvider bcsp
- = (BeanContextServiceProvider) services.get(serviceClass);
- return bcsp.getCurrentServiceSelectors(this, serviceClass);
+ synchronized (services)
+ {
+ BeanContextServiceProvider bcsp
+ = ((BCSSServiceProvider)
+ services.get(serviceClass)).getServiceProvider();
+ if (bcsp == null)
+ return null;
+ else
+ return bcsp.getCurrentServiceSelectors(this, serviceClass);
+ }
}
}
+ /**
+ * Retrieves the specified service. If a provider for the service
+ * is registered in this context, then the request is passed on to
+ * the provider and the service returned. Otherwise, the request
+ * is delegated to a parent {@link BeanContextServices}, if possible.
+ * If the service can not be found at all, then <code>null</code>
+ * is returned.
+ *
+ * @param child the child obtaining the reference.
+ * @param requestor the requestor of the service, which may be the
+ * child itself.
+ * @param serviceClass the service being requested.
+ * @param serviceSelector an additional service-dependent parameter
+ * (may be <code>null</code> if not appropriate).
+ * @param bcsrl a listener used to notify the requestor that the service
+ * has since been revoked.
+ * @return a reference to the service requested, or <code>null</code>.
+ * @throws TooManyListenersException according to Sun's documentation.
+ */
public Object getService (BeanContextChild child, Object requestor,
Class serviceClass, Object serviceSelector,
BeanContextServiceRevokedListener bcsrl)
- throws TooManyListenersException, NotImplementedException
+ throws TooManyListenersException
{
- throw new Error ("Not implemented");
+ synchronized (globalHierarchyLock)
+ {
+ synchronized (services)
+ {
+ Object service;
+ BeanContextServiceProvider provider = ((BCSSServiceProvider)
+ services.get(serviceClass)).getServiceProvider();
+ if (provider != null)
+ {
+ service = provider.getService(this, requestor, serviceClass,
+ serviceSelector);
+ List childServices = (List) serviceUsers.get(child);
+ if (childServices == null)
+ {
+ childServices = new ArrayList();
+ serviceUsers.put(child, childServices);
+ }
+ childServices.add(serviceClass);
+ }
+ else
+ {
+ BeanContextServices peer = getBeanContextServicesPeer();
+ if (peer != null)
+ service = peer.getService(child, requestor, serviceClass,
+ serviceSelector, bcsrl);
+ else
+ service = null;
+ }
+ if (service != null)
+ {
+ ServiceRequest request = new ServiceRequest(requestor, bcsrl);
+ Set requests = (Set) serviceRequests.get(serviceClass);
+ if (requests == null)
+ {
+ requests = new HashSet();
+ serviceRequests.put(serviceClass, requests);
+ }
+ requests.add(request);
+ ServiceLease lease = new ServiceLease(requestor, service);
+ serviceLeases.put(lease, provider);
+ }
+ return service;
+ }
+ }
}
+ /**
+ * Returns true if the specified service is available.
+ *
+ * @param serviceClass the service to check for.
+ * @return true if the service is available.
+ */
public boolean hasService (Class serviceClass)
{
- synchronized (services)
+ synchronized (globalHierarchyLock)
{
- return services.containsKey(serviceClass);
+ synchronized (services)
+ {
+ return services.containsKey(serviceClass);
+ }
}
}
@@ -323,6 +767,9 @@ public class BeanContextServicesSupport
bcsListeners = new ArrayList();
services = new HashMap();
+ serviceUsers = new HashMap();
+ serviceRequests = new HashMap();
+ serviceLeases = new HashMap();
}
protected void initializeBeanContextResources ()
@@ -337,11 +784,37 @@ public class BeanContextServicesSupport
throw new Error ("Not implemented");
}
+ /**
+ * Releases the reference to a service held by a
+ * {@link BeanContextChild} (or an arbitrary object associated
+ * with it). It simply calls the appropriate method on the
+ * underlying provider.
+ *
+ * @param child the child who holds the reference.
+ * @param requestor the object that requested the reference.
+ * @param service the service being released.
+ */
public void releaseService (BeanContextChild child, Object requestor,
Object service)
- throws NotImplementedException
{
- throw new Error ("Not implemented");
+ synchronized (globalHierarchyLock)
+ {
+ synchronized (services)
+ {
+ ServiceLease lease = new ServiceLease(requestor, service);
+ BeanContextServiceProvider provider = (BeanContextServiceProvider)
+ serviceLeases.get(lease);
+ if (provider != null)
+ provider.releaseService(this, requestor, service);
+ else
+ {
+ BeanContextServices peer = getBeanContextServicesPeer();
+ if (peer != null)
+ peer.releaseService(child, requestor, service);
+ }
+ serviceLeases.remove(lease);
+ }
+ }
}
public void removeBeanContextServicesListener
@@ -349,17 +822,35 @@ public class BeanContextServicesSupport
{
synchronized (bcsListeners)
{
- int index = bcsListeners.indexOf(listener);
- if (index > -1)
- bcsListeners.remove(index);
+ bcsListeners.remove(listener);
}
}
+ /**
+ * Revokes the given service. A {@link BeanContextServiceRevokedEvent} is
+ * emitted to all registered {@link BeanContextServiceRevokedListener}s
+ * and {@link BeanContextServiceListener}s. If <code>revokeCurrentServicesNow</code>
+ * is true, termination of the service is immediate. Otherwise, prior
+ * acquisitions of the service by requestors remain valid.
+ *
+ * @param serviceClass the service to revoke.
+ * @param bcsp the provider of the revoked service.
+ * @param revokeCurrentServicesNow true if this is an exceptional circumstance
+ * where service should be immediately revoked.
+ */
public void revokeService (Class serviceClass, BeanContextServiceProvider bcsp,
boolean revokeCurrentServicesNow)
- throws NotImplementedException
{
- throw new Error ("Not implemented");
+ synchronized (globalHierarchyLock)
+ {
+ synchronized (services)
+ {
+ fireServiceRevoked(serviceClass, revokeCurrentServicesNow);
+ services.remove(serviceClass);
+ if (bcsp instanceof Serializable)
+ --serializable;
+ }
+ }
}
public void serviceAvailable (BeanContextServiceAvailableEvent bcssae)
diff --git a/java/beans/beancontext/BeanContextSupport.java b/java/beans/beancontext/BeanContextSupport.java
index 2dc2a4e4a..d57f5f884 100644
--- a/java/beans/beancontext/BeanContextSupport.java
+++ b/java/beans/beancontext/BeanContextSupport.java
@@ -38,8 +38,6 @@ exception statement from your version. */
package java.beans.beancontext;
-import gnu.classpath.NotImplementedException;
-
import java.beans.Beans;
import java.beans.DesignMode;
import java.beans.PropertyChangeEvent;
@@ -57,6 +55,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
/**
@@ -74,20 +73,52 @@ public class BeanContextSupport extends BeanContextChildSupport
{
private static final long serialVersionUID = -4879613978649577204L;
- // This won't show up in japi, but we mark it as a stub anyway,
- // so that searches for NotImplementedException will find it.
+ /**
+ * Deserializes a stored bean context. Hook methods are provided to allow
+ * subclasses to perform their own deserialization after the default
+ * deserialization but prior to the deserialization of the children. Note that
+ * {@link #readChildren(ObjectInputStream)} is only called if there
+ * is no distinct peer. If there is, the peer is expected to call
+ * the method instead.
+ *
+ * @param s the stream to deserialize.
+ * @throws ClassNotFoundException if the class of an object being deserialized
+ * could not be found.
+ * @throws IOException if an I/O error occurs.
+ */
private void readObject (ObjectInputStream s)
- throws ClassNotFoundException, IOException, NotImplementedException
+ throws ClassNotFoundException, IOException
{
- throw new Error ("Not implemented");
+ s.defaultReadObject();
+ bcsPreDeserializationHook(s);
+ BeanContext peer = getBeanContextPeer();
+ if (peer == null || peer == this)
+ readChildren(s);
}
- // This won't show up in japi, but we mark it as a stub anyway,
- // so that searches for NotImplementedException will find it.
+ /**
+ * Serializes a bean context. Hook methods are provided to allow
+ * subclasses to perform their own serialization after the default
+ * serialization but prior to serialization of the children. Note that
+ * {@link #writeChildren(ObjectOutputStream)} is only called if there
+ * is no distinct peer. If there is, the peer is expected to call
+ * the method instead.
+ *
+ * @param s the stream to serialize.
+ * @throws ClassNotFoundException if the class of an object being deserialized
+ * could not be found.
+ * @throws IOException if an I/O error occurs.
+ */
private void writeObject (ObjectOutputStream s)
- throws ClassNotFoundException, IOException, NotImplementedException
+ throws ClassNotFoundException, IOException
{
- throw new Error ("Not implemented");
+ serializing = true;
+ s.defaultWriteObject();
+ bcsPreSerializationHook(s);
+ BeanContext peer = getBeanContextPeer();
+ if (peer == null || peer == this)
+ writeChildren(s);
+ serializing = false;
}
protected class BCSChild implements Serializable
@@ -102,6 +133,12 @@ public class BeanContextSupport extends BeanContextChildSupport
this.targetChild = targetChild;
this.peer = peer;
}
+
+ private Object getTargetChild()
+ {
+ return targetChild;
+ }
+
}
protected static final class BCSIterator implements Iterator
@@ -139,6 +176,8 @@ public class BeanContextSupport extends BeanContextChildSupport
protected transient boolean okToUseGui;
+ private transient boolean serializing;
+
/**
* Construct a BeanContextSupport instance.
*/
@@ -328,7 +367,6 @@ public class BeanContextSupport extends BeanContextChildSupport
* told not to use it.
*/
public boolean avoidingGui()
- throws NotImplementedException
{
return needsGui() && (!okToUseGui);
}
@@ -341,22 +379,49 @@ public class BeanContextSupport extends BeanContextChildSupport
}
}
+ /**
+ * Subclasses may use this method to perform their own deserialization
+ * after the default deserialization process has taken place, but
+ * prior to the deserialization of the children. It should not
+ * be used to replace the implementation of <code>readObject</code>
+ * in the subclass.
+ *
+ * @param ois the input stream.
+ * @throws ClassNotFoundException if the class of an object being deserialized
+ * could not be found.
+ * @throws IOException if an I/O error occurs.
+ */
protected void bcsPreDeserializationHook (ObjectInputStream ois)
- throws ClassNotFoundException, IOException, NotImplementedException
+ throws ClassNotFoundException, IOException
{
- throw new Error ("Not implemented");
+ /* Purposefully left empty */
}
+ /**
+ * Subclasses may use this method to perform their own serialization
+ * after the default serialization process has taken place, but
+ * prior to the serialization of the children. It should not
+ * be used to replace the implementation of <code>writeObject</code>
+ * in the subclass.
+ *
+ * @param oos the output stream.
+ * @throws IOException if an I/O error occurs.
+ */
protected void bcsPreSerializationHook (ObjectOutputStream oos)
- throws IOException, NotImplementedException
+ throws IOException
{
- throw new Error ("Not implemented");
+ /* Purposefully left empty */
}
+ /**
+ * Called when a child is deserialized.
+ *
+ * @param child the deserialized child.
+ * @param bcsc the deserialized context wrapper for the child.
+ */
protected void childDeserializedHook (Object child, BeanContextSupport.BCSChild bcsc)
- throws NotImplementedException
{
- throw new Error ("Not implemented");
+ // Do nothing in the base class.
}
protected void childJustAddedHook (Object child, BeanContextSupport.BCSChild bcsc)
@@ -423,10 +488,25 @@ public class BeanContextSupport extends BeanContextChildSupport
return new BCSChild(targetChild, peer);
}
+ /**
+ * Deserializes objects (written by {@link #serialize(ObjectOutputStream,
+ * Collection)}) and adds them to the specified collection.
+ *
+ * @param ois the input stream (<code>null</code> not permitted).
+ * @param coll the collection to add the objects to (<code>null</code> not
+ * permitted).
+ *
+ * @throws ClassNotFoundException
+ * @throws IOException
+ *
+ * @see #serialize(ObjectOutputStream, Collection)
+ */
protected final void deserialize (ObjectInputStream ois, Collection coll)
- throws ClassNotFoundException, IOException, NotImplementedException
+ throws ClassNotFoundException, IOException
{
- throw new Error ("Not implemented");
+ int itemCount = ois.readInt();
+ for (int i = 0; i < itemCount; i++)
+ coll.add(ois.readObject());
}
/**
@@ -502,34 +582,91 @@ public class BeanContextSupport extends BeanContextChildSupport
return null;
}
- protected static final BeanContextMembershipListener getChildBeanContextMembershipListener (Object child)
- throws NotImplementedException
+ /**
+ * Returns <code>child</code> as an instance of
+ * {@link BeanContextMembershipListener}, or <code>null</code> if
+ * <code>child</code> does not implement that interface.
+ *
+ * @param child the child (<code>null</code> permitted).
+ *
+ * @return The child cast to {@link BeanContextMembershipListener}.
+ */
+ protected static final BeanContextMembershipListener
+ getChildBeanContextMembershipListener(Object child)
{
- throw new Error ("Not implemented");
+ if (child instanceof BeanContextMembershipListener)
+ return (BeanContextMembershipListener) child;
+ else
+ return null;
}
- protected static final PropertyChangeListener getChildPropertyChangeListener (Object child)
- throws NotImplementedException
+ /**
+ * Returns <code>child</code> as an instance of
+ * {@link PropertyChangeListener}, or <code>null</code> if <code>child</code>
+ * does not implement that interface.
+ *
+ * @param child the child (<code>null</code> permitted).
+ *
+ * @return The child cast to {@link PropertyChangeListener}.
+ */
+ protected static final PropertyChangeListener getChildPropertyChangeListener(
+ Object child)
{
- throw new Error ("Not implemented");
+ if (child instanceof PropertyChangeListener)
+ return (PropertyChangeListener) child;
+ else
+ return null;
}
- protected static final Serializable getChildSerializable (Object child)
- throws NotImplementedException
+ /**
+ * Returns <code>child</code> as an instance of {@link Serializable}, or
+ * <code>null</code> if <code>child</code> does not implement that
+ * interface.
+ *
+ * @param child the child (<code>null</code> permitted).
+ *
+ * @return The child cast to {@link Serializable}.
+ */
+ protected static final Serializable getChildSerializable(Object child)
{
- throw new Error ("Not implemented");
+ if (child instanceof Serializable)
+ return (Serializable) child;
+ else
+ return null;
}
- protected static final VetoableChangeListener getChildVetoableChangeListener (Object child)
- throws NotImplementedException
+ /**
+ * Returns <code>child</code> as an instance of
+ * {@link VetoableChangeListener}, or <code>null</code> if <code>child</code>
+ * does not implement that interface.
+ *
+ * @param child the child (<code>null</code> permitted).
+ *
+ * @return The child cast to {@link VetoableChangeListener}.
+ */
+ protected static final VetoableChangeListener getChildVetoableChangeListener(
+ Object child)
{
- throw new Error ("Not implemented");
+ if (child instanceof VetoableChangeListener)
+ return (VetoableChangeListener) child;
+ else
+ return null;
}
- protected static final Visibility getChildVisibility (Object child)
- throws NotImplementedException
+ /**
+ * Returns <code>child</code> as an instance of {@link Visibility}, or
+ * <code>null</code> if <code>child</code> does not implement that interface.
+ *
+ * @param child the child (<code>null</code> permitted).
+ *
+ * @return The child cast to {@link Visibility}.
+ */
+ protected static final Visibility getChildVisibility(Object child)
{
- throw new Error ("Not implemented");
+ if (child instanceof Visibility)
+ return (Visibility) child;
+ else
+ return null;
}
public Locale getLocale ()
@@ -577,7 +714,15 @@ public class BeanContextSupport extends BeanContextChildSupport
return Beans.instantiate(getClass().getClassLoader(), beanName, this);
}
- public boolean isDesignTime ()
+ /**
+ * Returns <code>true</code> if the <code>BeanContext</code> is in
+ * design time mode, and <code>false</code> if it is in runtime mode.
+ *
+ * @return A boolean.
+ *
+ * @see #setDesignTime(boolean)
+ */
+ public boolean isDesignTime()
{
return designTime;
}
@@ -595,10 +740,15 @@ public class BeanContextSupport extends BeanContextChildSupport
}
}
- public boolean isSerializing ()
- throws NotImplementedException
+ /**
+ * Returns true if the bean context is in the process
+ * of being serialized.
+ *
+ * @return true if the context is being serialized.
+ */
+ public boolean isSerializing()
{
- throw new Error ("Not implemented");
+ return serializing;
}
public Iterator iterator ()
@@ -643,10 +793,33 @@ public class BeanContextSupport extends BeanContextChildSupport
remove(pce.getSource(), false);
}
+ /**
+ * Deerializes the children using the
+ * {@link #deserialize(ObjectInputStream, Collection} method
+ * and then calls {@link childDeserializedHook(Object, BCSChild)}
+ * for each child deserialized.
+ *
+ * @param oos the output stream.
+ * @throws IOException if an I/O error occurs.
+ */
public final void readChildren (ObjectInputStream ois)
- throws IOException, ClassNotFoundException, NotImplementedException
+ throws IOException, ClassNotFoundException
{
- throw new Error ("Not implemented");
+ List temp = new ArrayList();
+ deserialize(ois, temp);
+ Iterator i = temp.iterator();
+ synchronized (globalHierarchyLock)
+ {
+ synchronized (children)
+ {
+ while (i.hasNext())
+ {
+ BCSChild bcs = (BCSChild) i.next();
+ childDeserializedHook(bcs.getTargetChild(), bcs);
+ children.put(bcs.getTargetChild(), bcs);
+ }
+ }
+ }
}
/**
@@ -689,7 +862,7 @@ public class BeanContextSupport extends BeanContextChildSupport
* This method is synchronized over the global hierarchy lock.
* </p>
*
- * @param targetChild the child to add.
+ * @param targetChild the child to remove.
* @param callChildSetBC true if the <code>setBeanContext()</code>
* method of the child should be called.
* @return false if the child doesn't exist.
@@ -765,17 +938,55 @@ public class BeanContextSupport extends BeanContextChildSupport
throw new UnsupportedOperationException();
}
- protected final void serialize (ObjectOutputStream oos, Collection coll)
- throws IOException, NotImplementedException
+ /**
+ * Writes the items in the collection to the specified output stream. Items
+ * in the collection that are not instances of {@link Serializable}
+ * (this includes <code>null</code>) are simply ignored.
+ *
+ * @param oos the output stream (<code>null</code> not permitted).
+ * @param coll the collection (<code>null</code> not permitted).
+ *
+ * @throws IOException
+ *
+ * @see #deserialize(ObjectInputStream, Collection)
+ */
+ protected final void serialize(ObjectOutputStream oos, Collection coll)
+ throws IOException
{
- throw new Error ("Not implemented");
+ Object[] items = coll.toArray();
+ int itemCount = 0;
+ for (int i = 0; i < items.length; i++)
+ {
+ if (items[i] instanceof Serializable)
+ itemCount++;
+ }
+ oos.writeInt(itemCount);
+ for (int i = 0; i < items.length; i++)
+ {
+ if (items[i] instanceof Serializable)
+ oos.writeObject(items[i]);
+ }
}
- public void setDesignTime (boolean dtime)
+ /**
+ * Sets the flag that indicates whether or not the
+ * <code>BeanContext</code> is in design mode. If the flag changes
+ * value, a {@link PropertyChangeEvent} (with the property name 'designMode')
+ * is sent to registered listeners. Note that the property name used here
+ * does NOT match the specification in the {@link DesignMode} interface, we
+ * match the reference implementation instead - see bug parade entry 4295174.
+ *
+ * @param dtime the new value for the flag.
+ *
+ * @see #isDesignTime()
+ */
+ public void setDesignTime(boolean dtime)
{
boolean save = designTime;
designTime = dtime;
- firePropertyChange(DesignMode.PROPERTYNAME, Boolean.valueOf(save),
+ // note that we use the same property name as Sun's implementation,
+ // even though this is a known bug: see bug parade entry 4295174
+ firePropertyChange("designMode", Boolean.valueOf(save),
Boolean.valueOf(dtime));
}
@@ -798,7 +1009,12 @@ public class BeanContextSupport extends BeanContextChildSupport
}
}
- public Object[] toArray ()
+ /**
+ * Returns an array containing the children of this <code>BeanContext</code>.
+ *
+ * @return An array containing the children.
+ */
+ public Object[] toArray()
{
synchronized (children)
{
@@ -806,10 +1022,16 @@ public class BeanContextSupport extends BeanContextChildSupport
}
}
+ /**
+ * Populates, then returns, the supplied array with the children of this
+ * <code>BeanContext</code>. If the array is too short to hold the
+ * children, a new array is allocated and returned. If the array is too
+ * long, it is padded with <code>null</code> items at the end.
+ *
+ * @param array an array to populate (<code>null</code> not permitted).
+ */
public Object[] toArray(Object[] array)
- throws NotImplementedException
{
- // This implementation is incorrect, I think.
synchronized (children)
{
return children.keySet().toArray(array);
@@ -838,9 +1060,20 @@ public class BeanContextSupport extends BeanContextChildSupport
/* Purposefully left empty */
}
+ /**
+ * Serializes the children using the
+ * {@link #serialize(ObjectOutputStream, Collection} method.
+ *
+ * @param oos the output stream.
+ * @throws IOException if an I/O error occurs.
+ */
public final void writeChildren (ObjectOutputStream oos)
- throws IOException, NotImplementedException
+ throws IOException
{
- throw new Error ("Not implemented");
+ synchronized (children)
+ {
+ serialize(oos, children.values());
+ }
}
+
}
diff --git a/java/io/File.java b/java/io/File.java
index 4f5ab4e66..5d1b3ec85 100644
--- a/java/io/File.java
+++ b/java/io/File.java
@@ -754,8 +754,9 @@ public class File implements Serializable, Comparable<File>
String files[] = VMFile.list(path);
// Check if an error occured in listInternal().
+ // This is an unreadable directory, pretend there is nothing inside.
if (files == null)
- return null;
+ return new String[0];
if (filter == null)
return files;
diff --git a/java/net/DatagramSocket.java b/java/net/DatagramSocket.java
index 974827cbb..d7aad7222 100644
--- a/java/net/DatagramSocket.java
+++ b/java/net/DatagramSocket.java
@@ -325,7 +325,7 @@ public class DatagramSocket
SecurityManager s = System.getSecurityManager();
if (s != null)
- s.checkConnect(localAddr.getHostName(), -1);
+ s.checkConnect(localAddr.getHostAddress(), -1);
}
catch (SecurityException e)
{
@@ -525,7 +525,7 @@ public class DatagramSocket
SecurityManager sm = System.getSecurityManager();
if (sm != null)
- sm.checkConnect(address.getHostName(), port);
+ sm.checkConnect(address.getHostAddress(), port);
try
{
@@ -608,7 +608,7 @@ public class DatagramSocket
SecurityManager s = System.getSecurityManager();
if (s != null && isConnected())
- s.checkAccept(p.getAddress().getHostName(), p.getPort());
+ s.checkAccept(p.getAddress().getHostAddress(), p.getPort());
}
/**
diff --git a/java/text/Bidi.java b/java/text/Bidi.java
index 05b10f52d..491f9e36e 100644
--- a/java/text/Bidi.java
+++ b/java/text/Bidi.java
@@ -991,7 +991,8 @@ public final class Bidi
&& dir != Character.DIRECTIONALITY_ARABIC_NUMBER
&& dir != Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR
&& dir != Character.DIRECTIONALITY_SEGMENT_SEPARATOR
- && dir != Character.DIRECTIONALITY_WHITESPACE)
+ && dir != Character.DIRECTIONALITY_WHITESPACE
+ && dir != Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR)
return true;
}
diff --git a/java/text/DecimalFormat.java b/java/text/DecimalFormat.java
index f64249b67..3b67a50da 100644
--- a/java/text/DecimalFormat.java
+++ b/java/text/DecimalFormat.java
@@ -35,387 +35,157 @@ this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
+/*
+ * This class contains few bits from ICU4J (http://icu.sourceforge.net/),
+ * Copyright by IBM and others and distributed under the
+ * distributed under MIT/X.
+ */
+
package java.text;
-import gnu.java.text.AttributedFormatBuffer;
-import gnu.java.text.FormatBuffer;
-import gnu.java.text.FormatCharacterIterator;
-import gnu.java.text.StringFormatBuffer;
+import java.math.BigDecimal;
+import java.math.BigInteger;
-import java.io.IOException;
-import java.io.ObjectInputStream;
+import java.util.ArrayList;
import java.util.Currency;
-import java.util.HashMap;
import java.util.Locale;
-/**
+/*
+ * This note is here for historical reasons and because I had not the courage
+ * to remove it :)
+ *
* @author Tom Tromey (tromey@cygnus.com)
* @author Andrew John Hughes (gnu_andrew@member.fsf.org)
* @date March 4, 1999
- */
-/* Written using "Java Class Libraries", 2nd edition, plus online
+ *
+ * Written using "Java Class Libraries", 2nd edition, plus online
* API docs for JDK 1.2 from http://www.javasoft.com.
* Status: Believed complete and correct to 1.2.
* Note however that the docs are very unclear about how format parsing
* should work. No doubt there are problems here.
*/
+
+/**
+ * This class is a concrete implementation of NumberFormat used to format
+ * decimal numbers. The class can format numbers given a specific locale.
+ * Generally, to get an instance of DecimalFormat you should call the factory
+ * methods in the <code>NumberFormat</code> base class.
+ *
+ * @author Mario Torre <neugens@limasoftware.net>
+ * @author Tom Tromey (tromey@cygnus.com)
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ */
public class DecimalFormat extends NumberFormat
{
- // This is a helper for applyPatternWithSymbols. It reads a prefix
- // or a suffix. It can cause some side-effects.
- private int scanFix (String pattern, int index, FormatBuffer buf,
- String patChars, DecimalFormatSymbols syms,
- boolean is_suffix)
- {
- int len = pattern.length();
- boolean quoteStarted = false;
- buf.clear();
-
- boolean multiplierSet = false;
- while (index < len)
- {
- char c = pattern.charAt(index);
-
- if (quoteStarted)
- {
- if (c == '\'')
- quoteStarted = false;
- else
- buf.append(c);
- index++;
- continue;
- }
-
- if (c == '\'' && index + 1 < len
- && pattern.charAt(index + 1) == '\'')
- {
- buf.append(c);
- index++;
- }
- else if (c == '\'')
- {
- quoteStarted = true;
- }
- else if (c == '\u00a4')
- {
- /* Currency interpreted later */
- buf.append(c);
- }
- else if (c == syms.getPercent())
- {
- if (multiplierSet)
- throw new IllegalArgumentException ("multiplier already set " +
- "- index: " + index);
- multiplierSet = true;
- multiplier = 100;
- buf.append(c, NumberFormat.Field.PERCENT);
- }
- else if (c == syms.getPerMill())
- {
- if (multiplierSet)
- throw new IllegalArgumentException ("multiplier already set " +
- "- index: " + index);
- multiplierSet = true;
- multiplier = 1000;
- buf.append(c, NumberFormat.Field.PERMILLE);
- }
- else if (patChars.indexOf(c) != -1)
- {
- // This is a pattern character.
- break;
- }
- else
- {
- buf.append(c);
- }
- index++;
- }
-
- if (quoteStarted)
- throw new IllegalArgumentException ("pattern is lacking a closing quote");
-
- return index;
- }
-
- // A helper which reads a number format.
- private int scanFormat (String pattern, int index, String patChars,
- DecimalFormatSymbols syms, boolean is_positive)
- {
- int max = pattern.length();
-
- int countSinceGroup = 0;
- int zeroCount = 0;
- boolean saw_group = false;
-
- //
- // Scan integer part.
- //
- while (index < max)
- {
- char c = pattern.charAt(index);
-
- if (c == syms.getDigit())
- {
- if (zeroCount > 0)
- throw new IllegalArgumentException ("digit mark following " +
- "zero - index: " + index);
- ++countSinceGroup;
- }
- else if (c == syms.getZeroDigit())
- {
- ++zeroCount;
- ++countSinceGroup;
- }
- else if (c == syms.getGroupingSeparator())
- {
- countSinceGroup = 0;
- saw_group = true;
- }
- else
- break;
-
- ++index;
- }
-
- // We can only side-effect when parsing the positive format.
- if (is_positive)
- {
- groupingUsed = saw_group;
- groupingSize = (byte) countSinceGroup;
- // Checking "zeroCount > 0" avoids 0 being formatted into "" with "#".
- if (zeroCount > 0)
- minimumIntegerDigits = zeroCount;
- }
-
- // Early termination.
- if (index == max || pattern.charAt(index) == syms.getGroupingSeparator())
- {
- if (is_positive)
- decimalSeparatorAlwaysShown = false;
- return index;
- }
-
- if (pattern.charAt(index) == syms.getDecimalSeparator())
- {
- ++index;
-
- //
- // Scan fractional part.
- //
- int hashCount = 0;
- zeroCount = 0;
- while (index < max)
- {
- char c = pattern.charAt(index);
- if (c == syms.getZeroDigit())
- {
- if (hashCount > 0)
- throw new IllegalArgumentException ("zero mark " +
- "following digit - index: " + index);
- ++zeroCount;
- }
- else if (c == syms.getDigit())
- {
- ++hashCount;
- }
- else if (c != syms.getExponential()
- && c != syms.getPatternSeparator()
- && c != syms.getPercent()
- && c != syms.getPerMill()
- && patChars.indexOf(c) != -1)
- throw new IllegalArgumentException ("unexpected special " +
- "character - index: " + index);
- else
- break;
-
- ++index;
- }
-
- if (is_positive)
- {
- maximumFractionDigits = hashCount + zeroCount;
- minimumFractionDigits = zeroCount;
- }
-
- if (index == max)
- return index;
- }
-
- if (pattern.charAt(index) == syms.getExponential())
- {
- //
- // Scan exponential format.
- //
- zeroCount = 0;
- ++index;
- while (index < max)
- {
- char c = pattern.charAt(index);
- if (c == syms.getZeroDigit())
- ++zeroCount;
- else if (c == syms.getDigit())
- {
- if (zeroCount > 0)
- throw new
- IllegalArgumentException ("digit mark following zero " +
- "in exponent - index: " +
- index);
- }
- else if (patChars.indexOf(c) != -1)
- throw new IllegalArgumentException ("unexpected special " +
- "character - index: " +
- index);
- else
- break;
-
- ++index;
- }
-
- if (is_positive)
- {
- useExponentialNotation = true;
- minExponentDigits = (byte) zeroCount;
- }
-
- maximumIntegerDigits = groupingSize;
- groupingSize = 0;
- if (maximumIntegerDigits > minimumIntegerDigits && maximumIntegerDigits > 0)
- {
- minimumIntegerDigits = 1;
- exponentRound = maximumIntegerDigits;
- }
- else
- exponentRound = 1;
- }
-
- return index;
- }
-
- // This helper function creates a string consisting of all the
- // characters which can appear in a pattern and must be quoted.
- private String patternChars (DecimalFormatSymbols syms)
- {
- StringBuffer buf = new StringBuffer ();
- buf.append(syms.getDecimalSeparator());
- buf.append(syms.getDigit());
- buf.append(syms.getExponential());
- buf.append(syms.getGroupingSeparator());
- // Adding this one causes pattern application to fail.
- // Of course, omitting is causes toPattern to fail.
- // ... but we already have bugs there. FIXME.
- // buf.append(syms.getMinusSign());
- buf.append(syms.getPatternSeparator());
- buf.append(syms.getPercent());
- buf.append(syms.getPerMill());
- buf.append(syms.getZeroDigit());
- buf.append('\u00a4');
- return buf.toString();
- }
-
- private void applyPatternWithSymbols(String pattern, DecimalFormatSymbols syms)
- {
- // Initialize to the state the parser expects.
- negativePrefix = "";
- negativeSuffix = "";
- positivePrefix = "";
- positiveSuffix = "";
- decimalSeparatorAlwaysShown = false;
- groupingSize = 0;
- minExponentDigits = 0;
- multiplier = 1;
- useExponentialNotation = false;
- groupingUsed = false;
- maximumFractionDigits = 0;
- maximumIntegerDigits = MAXIMUM_INTEGER_DIGITS;
- minimumFractionDigits = 0;
- minimumIntegerDigits = 1;
-
- AttributedFormatBuffer buf = new AttributedFormatBuffer ();
- String patChars = patternChars (syms);
-
- int max = pattern.length();
- int index = scanFix (pattern, 0, buf, patChars, syms, false);
- buf.sync();
- positivePrefix = buf.getBuffer().toString();
- positivePrefixRanges = buf.getRanges();
- positivePrefixAttrs = buf.getAttributes();
-
- index = scanFormat (pattern, index, patChars, syms, true);
-
- index = scanFix (pattern, index, buf, patChars, syms, true);
- buf.sync();
- positiveSuffix = buf.getBuffer().toString();
- positiveSuffixRanges = buf.getRanges();
- positiveSuffixAttrs = buf.getAttributes();
-
- if (index == pattern.length())
- {
- // No negative info.
- negativePrefix = null;
- negativeSuffix = null;
- }
- else
- {
- if (pattern.charAt(index) != syms.getPatternSeparator())
- throw new IllegalArgumentException ("separator character " +
- "expected - index: " + index);
-
- index = scanFix (pattern, index + 1, buf, patChars, syms, false);
- buf.sync();
- negativePrefix = buf.getBuffer().toString();
- negativePrefixRanges = buf.getRanges();
- negativePrefixAttrs = buf.getAttributes();
-
- // We parse the negative format for errors but we don't let
- // it side-effect this object.
- index = scanFormat (pattern, index, patChars, syms, false);
-
- index = scanFix (pattern, index, buf, patChars, syms, true);
- buf.sync();
- negativeSuffix = buf.getBuffer().toString();
- negativeSuffixRanges = buf.getRanges();
- negativeSuffixAttrs = buf.getAttributes();
-
- if (index != pattern.length())
- throw new IllegalArgumentException ("end of pattern expected " +
- "- index: " + index);
- }
- }
-
- public void applyLocalizedPattern (String pattern)
- {
- // JCL p. 638 claims this throws a ParseException but p. 629
- // contradicts this. Empirical tests with patterns of "0,###.0"
- // and "#.#.#" corroborate the p. 629 statement that an
- // IllegalArgumentException is thrown.
- applyPatternWithSymbols (pattern, symbols);
- }
-
- public void applyPattern (String pattern)
- {
- // JCL p. 638 claims this throws a ParseException but p. 629
- // contradicts this. Empirical tests with patterns of "0,###.0"
- // and "#.#.#" corroborate the p. 629 statement that an
- // IllegalArgumentException is thrown.
- applyPatternWithSymbols (pattern, nonLocalizedSymbols);
- }
-
- public Object clone ()
- {
- DecimalFormat c = (DecimalFormat) super.clone ();
- c.symbols = (DecimalFormatSymbols) symbols.clone ();
- return c;
- }
+ /** serialVersionUID for serializartion. */
+ private static final long serialVersionUID = 864413376551465018L;
+
+ /** Defines the default number of digits allowed while formatting integers. */
+ private static final int DEFAULT_INTEGER_DIGITS = 309;
/**
+ * Defines the default number of digits allowed while formatting
+ * fractions.
+ */
+ private static final int DEFAULT_FRACTION_DIGITS = 340;
+
+ /**
+ * Locale-independent pattern symbols.
+ */
+ // Happen to be the same as the US symbols.
+ private static final DecimalFormatSymbols nonLocalizedSymbols
+ = new DecimalFormatSymbols (Locale.US);
+
+ /**
+ * Defines if parse should return a BigDecimal or not.
+ */
+ private boolean parseBigDecimal;
+
+ /**
+ * Defines if we have to use the monetary decimal separator or
+ * the decimal separator while formatting numbers.
+ */
+ private boolean useCurrencySeparator;
+
+ /** Defines if the decimal separator is always shown or not. */
+ private boolean decimalSeparatorAlwaysShown;
+
+ /**
+ * Defines if the decimal separator has to be shown.
+ *
+ * This is different then <code>decimalSeparatorAlwaysShown</code>,
+ * as it defines if the format string contains a decimal separator or no.
+ */
+ private boolean showDecimalSeparator;
+
+ /**
+ * This field is used to determine if the grouping
+ * separator is included in the format string or not.
+ * This is only needed to match the behaviour of the RI.
+ */
+ private boolean groupingSeparatorInPattern;
+
+ /** Defines the size of grouping groups when grouping is used. */
+ private byte groupingSize;
+
+ /**
+ * This is an internal parameter used to keep track of the number
+ * of digits the form the exponent, when exponential notation is used.
+ * It is used with <code>exponentRound</code>
+ */
+ private byte minExponentDigits;
+
+ /** This field is used to set the exponent in the engineering notation. */
+ private int exponentRound;
+
+ /** Multiplier used in percent style formats. */
+ private int multiplier;
+
+ /** Multiplier used in percent style formats. */
+ private int negativePatternMultiplier;
+
+ /** The negative prefix. */
+ private String negativePrefix;
+
+ /** The negative suffix. */
+ private String negativeSuffix;
+
+ /** The positive prefix. */
+ private String positivePrefix;
+
+ /** The positive suffix. */
+ private String positiveSuffix;
+
+ /** Decimal Format Symbols for the given locale. */
+ private DecimalFormatSymbols symbols;
+
+ /** Determine if we have to use exponential notation or not. */
+ private boolean useExponentialNotation;
+
+ /**
+ * Defines the maximum number of integer digits to show when we use
+ * the exponential notation.
+ */
+ private int maxIntegerDigitsExponent;
+
+ /** Defines if the format string has a negative prefix or not. */
+ private boolean hasNegativePrefix;
+
+ /** Defines if the format string has a fractional pattern or not. */
+ private boolean hasFractionalPattern;
+
+ /** Stores a list of attributes for use by formatToCharacterIterator. */
+ private ArrayList attributes = new ArrayList();
+
+ /**
* Constructs a <code>DecimalFormat</code> which uses the default
* pattern and symbols.
*/
- public DecimalFormat ()
+ public DecimalFormat()
{
this ("#,##0.###");
}
-
+
/**
* Constructs a <code>DecimalFormat</code> which uses the given
* pattern and the default symbols for formatting and parsing.
@@ -424,9 +194,9 @@ public class DecimalFormat extends NumberFormat
* @throws NullPointerException if any argument is null.
* @throws IllegalArgumentException if the pattern is invalid.
*/
- public DecimalFormat (String pattern)
+ public DecimalFormat(String pattern)
{
- this (pattern, new DecimalFormatSymbols ());
+ this (pattern, new DecimalFormatSymbols());
}
/**
@@ -442,14 +212,38 @@ public class DecimalFormat extends NumberFormat
public DecimalFormat(String pattern, DecimalFormatSymbols symbols)
{
this.symbols = (DecimalFormatSymbols) symbols.clone();
- applyPattern(pattern);
+ applyPatternWithSymbols(pattern, nonLocalizedSymbols);
+ }
+
+ /**
+ * Apply the given localized patern to the current DecimalFormat object.
+ *
+ * @param pattern The localized pattern to apply.
+ * @throws IllegalArgumentException if the given pattern is invalid.
+ * @throws NullPointerException if the input pattern is null.
+ */
+ public void applyLocalizedPattern (String pattern)
+ {
+ applyPatternWithSymbols(pattern, this.symbols);
}
- private boolean equals(String s1, String s2)
+ /**
+ * Apply the given localized pattern to the current DecimalFormat object.
+ *
+ * @param pattern The localized pattern to apply.
+ * @throws IllegalArgumentException if the given pattern is invalid.
+ * @throws NullPointerException if the input pattern is null.
+ */
+ public void applyPattern(String pattern)
{
- if (s1 == null || s2 == null)
- return s1 == s2;
- return s1.equals(s2);
+ applyPatternWithSymbols(pattern, nonLocalizedSymbols);
+ }
+
+ public Object clone()
+ {
+ DecimalFormat c = (DecimalFormat) super.clone();
+ c.symbols = (DecimalFormatSymbols) symbols.clone();
+ return c;
}
/**
@@ -471,8 +265,9 @@ public class DecimalFormat extends NumberFormat
return false;
DecimalFormat dup = (DecimalFormat) obj;
return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown
- && groupingUsed == dup.groupingUsed
- && groupingSize == dup.groupingSize
+ && groupingUsed == dup.groupingUsed
+ && groupingSeparatorInPattern == dup.groupingSeparatorInPattern
+ && groupingSize == dup.groupingSize
&& multiplier == dup.multiplier
&& useExponentialNotation == dup.useExponentialNotation
&& minExponentDigits == dup.minExponentDigits
@@ -480,6 +275,14 @@ public class DecimalFormat extends NumberFormat
&& maximumIntegerDigits == dup.maximumIntegerDigits
&& minimumFractionDigits == dup.minimumFractionDigits
&& maximumFractionDigits == dup.maximumFractionDigits
+ && parseBigDecimal == dup.parseBigDecimal
+ && useCurrencySeparator == dup.useCurrencySeparator
+ && showDecimalSeparator == dup.showDecimalSeparator
+ && exponentRound == dup.exponentRound
+ && negativePatternMultiplier == dup.negativePatternMultiplier
+ && maxIntegerDigitsExponent == dup.maxIntegerDigitsExponent
+ // XXX: causes equivalent patterns to fail
+ // && hasNegativePrefix == dup.hasNegativePrefix
&& equals(negativePrefix, dup.negativePrefix)
&& equals(negativeSuffix, dup.negativeSuffix)
&& equals(positivePrefix, dup.positivePrefix)
@@ -487,306 +290,163 @@ public class DecimalFormat extends NumberFormat
&& symbols.equals(dup.symbols));
}
- private void formatInternal (double number, FormatBuffer dest,
- FieldPosition fieldPos)
+ /**
+ * Returns a hash code for this object.
+ *
+ * @return A hash code.
+ */
+ public int hashCode()
{
- // A very special case.
- if (Double.isNaN(number))
- {
- dest.append(symbols.getNaN());
- if (fieldPos != null &&
- (fieldPos.getField() == INTEGER_FIELD ||
- fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
- {
- int index = dest.length();
- fieldPos.setBeginIndex(index - symbols.getNaN().length());
- fieldPos.setEndIndex(index);
- }
- return;
- }
-
- boolean is_neg = number < 0;
- if (is_neg)
- {
- if (negativePrefix != null)
- {
- dest.append(substituteCurrency(negativePrefix, number),
- negativePrefixRanges, negativePrefixAttrs);
- }
- else
- {
- dest.append(symbols.getMinusSign(), NumberFormat.Field.SIGN);
- dest.append(substituteCurrency(positivePrefix, number),
- positivePrefixRanges, positivePrefixAttrs);
- }
- number = - number;
- }
- else
+ return toPattern().hashCode();
+ }
+
+ /**
+ * Produce a formatted {@link String} representation of this object.
+ * The passed object must be of type number.
+ *
+ * @param obj The {@link Number} to format.
+ * @param sbuf The destination String; text will be appended to this String.
+ * @param pos If used on input can be used to define an alignment
+ * field. If used on output defines the offsets of the alignment field.
+ * @return The String representation of this long.
+ */
+ public StringBuffer format(Object obj, StringBuffer sbuf, FieldPosition pos)
+ {
+ if (obj instanceof BigInteger)
{
- dest.append(substituteCurrency(positivePrefix, number),
- positivePrefixRanges, positivePrefixAttrs);
+ BigDecimal decimal = new BigDecimal((BigInteger) obj);
+ formatInternal(decimal, true, sbuf, pos);
+ return sbuf;
}
- int integerBeginIndex = dest.length();
- int integerEndIndex = 0;
- int zeroStart = symbols.getZeroDigit() - '0';
-
- if (Double.isInfinite (number))
+ else if (obj instanceof BigDecimal)
{
- dest.append(symbols.getInfinity());
- integerEndIndex = dest.length();
+ formatInternal((BigDecimal) obj, true, sbuf, pos);
+ return sbuf;
}
- else
- {
- number *= multiplier;
-
- // Compute exponent.
- long exponent = 0;
- double baseNumber;
- if (useExponentialNotation && number > 0)
- {
- exponent = (long) Math.floor (Math.log10(number));
- exponent = exponent - (exponent % exponentRound);
- if (minimumIntegerDigits > 0)
- exponent -= minimumIntegerDigits - 1;
- baseNumber = (number / Math.pow(10.0, exponent));
- }
- else
- baseNumber = number;
-
- // Round to the correct number of digits.
- baseNumber += 5 * Math.pow(10.0, - maximumFractionDigits - 1);
-
- int index = dest.length();
- //double intPart = Math.floor(baseNumber);
- String intPart = Long.toString((long)Math.floor(baseNumber));
- int count, groupPosition = intPart.length();
-
- dest.setDefaultAttribute(NumberFormat.Field.INTEGER);
-
- for (count = 0; count < minimumIntegerDigits-intPart.length(); count++)
- dest.append(symbols.getZeroDigit());
-
- for (count = 0;
- count < maximumIntegerDigits && count < intPart.length();
- count++)
- {
- int dig = intPart.charAt(count);
-
- // Append group separator if required.
- if (groupingUsed && count > 0 && groupingSize != 0 && groupPosition % groupingSize == 0)
- {
- dest.append(symbols.getGroupingSeparator(), NumberFormat.Field.GROUPING_SEPARATOR);
- dest.setDefaultAttribute(NumberFormat.Field.INTEGER);
- }
- dest.append((char) (zeroStart + dig));
-
- groupPosition--;
- }
- dest.setDefaultAttribute(null);
-
- integerEndIndex = dest.length();
-
- int decimal_index = integerEndIndex;
- int consecutive_zeros = 0;
- int total_digits = 0;
-
- int localMaximumFractionDigits = maximumFractionDigits;
-
- if (useExponentialNotation)
- localMaximumFractionDigits += minimumIntegerDigits - count;
-
- // Strip integer part from NUMBER.
- double fracPart = baseNumber - Math.floor(baseNumber);
-
- if ( ((fracPart != 0 || minimumFractionDigits > 0) && localMaximumFractionDigits > 0)
- || decimalSeparatorAlwaysShown)
- {
- dest.append (symbols.getDecimalSeparator(), NumberFormat.Field.DECIMAL_SEPARATOR);
- }
-
- int fraction_begin = dest.length();
- dest.setDefaultAttribute(NumberFormat.Field.FRACTION);
- for (count = 0;
- count < localMaximumFractionDigits
- && (fracPart != 0 || count < minimumFractionDigits);
- ++count)
- {
- ++total_digits;
- fracPart *= 10;
- long dig = (long) fracPart;
- if (dig == 0)
- ++consecutive_zeros;
- else
- consecutive_zeros = 0;
- dest.append((char) (symbols.getZeroDigit() + dig));
-
- // Strip integer part from FRACPART.
- fracPart = fracPart - Math.floor (fracPart);
- }
-
- // Strip extraneous trailing `0's. We can't always detect
- // these in the loop.
- int extra_zeros = Math.min (consecutive_zeros,
- total_digits - minimumFractionDigits);
- if (extra_zeros > 0)
- {
- dest.cutTail(extra_zeros);
- total_digits -= extra_zeros;
- if (total_digits == 0 && !decimalSeparatorAlwaysShown)
- dest.cutTail(1);
- }
-
- if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
- {
- fieldPos.setBeginIndex(fraction_begin);
- fieldPos.setEndIndex(dest.length());
- }
-
- // Finally, print the exponent.
- if (useExponentialNotation)
- {
- dest.append(symbols.getExponential(), NumberFormat.Field.EXPONENT_SYMBOL);
- if (exponent < 0)
- {
- dest.append (symbols.getMinusSign (), NumberFormat.Field.EXPONENT_SIGN);
- exponent = - exponent;
- }
- index = dest.length();
- dest.setDefaultAttribute(NumberFormat.Field.EXPONENT);
- String exponentString = Long.toString ((long) exponent);
-
- for (count = 0; count < minExponentDigits-exponentString.length();
- count++)
- dest.append((char) symbols.getZeroDigit());
-
- for (count = 0;
- count < exponentString.length();
- ++count)
- {
- int dig = exponentString.charAt(count);
- dest.append((char) (zeroStart + dig));
- }
- }
- }
-
- if (fieldPos != null &&
- (fieldPos.getField() == INTEGER_FIELD ||
- fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
+
+ return super.format(obj, sbuf, pos);
+ }
+
+ /**
+ * Produce a formatted {@link String} representation of this double.
+ *
+ * @param number The double to format.
+ * @param dest The destination String; text will be appended to this String.
+ * @param fieldPos If used on input can be used to define an alignment
+ * field. If used on output defines the offsets of the alignment field.
+ * @return The String representation of this long.
+ * @throws NullPointerException if <code>dest</code> or fieldPos are null
+ */
+ public StringBuffer format(double number, StringBuffer dest,
+ FieldPosition fieldPos)
+ {
+ // special cases for double: NaN and negative or positive infinity
+ if (Double.isNaN(number))
{
- fieldPos.setBeginIndex(integerBeginIndex);
- fieldPos.setEndIndex(integerEndIndex);
+ // 1. NaN
+ String nan = symbols.getNaN();
+ dest.append(nan);
+
+ // update field position if required
+ if ((fieldPos.getField() == INTEGER_FIELD ||
+ fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
+ {
+ int index = dest.length();
+ fieldPos.setBeginIndex(index - nan.length());
+ fieldPos.setEndIndex(index);
+ }
}
-
- if (is_neg && negativeSuffix != null)
+ else if (Double.isInfinite(number))
{
- dest.append(substituteCurrency(negativeSuffix, number),
- negativeSuffixRanges, negativeSuffixAttrs);
+ // 2. Infinity
+ if (number < 0)
+ dest.append(this.negativePrefix);
+ else
+ dest.append(this.positivePrefix);
+
+ dest.append(symbols.getInfinity());
+
+ if (number < 0)
+ dest.append(this.negativeSuffix);
+ else
+ dest.append(this.positiveSuffix);
+
+ if ((fieldPos.getField() == INTEGER_FIELD ||
+ fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
+ {
+ fieldPos.setBeginIndex(dest.length());
+ fieldPos.setEndIndex(0);
+ }
}
else
{
- dest.append(substituteCurrency(positiveSuffix, number),
- positiveSuffixRanges, positiveSuffixAttrs);
+ // get the number as a BigDecimal
+ BigDecimal bigDecimal = new BigDecimal(String.valueOf(number));
+ formatInternal(bigDecimal, false, dest, fieldPos);
}
+
+ return dest;
}
- public StringBuffer format (double number, StringBuffer dest,
- FieldPosition fieldPos)
+ /**
+ * Produce a formatted {@link String} representation of this long.
+ *
+ * @param number The long to format.
+ * @param dest The destination String; text will be appended to this String.
+ * @param fieldPos If used on input can be used to define an alignment
+ * field. If used on output defines the offsets of the alignment field.
+ * @return The String representation of this long.
+ */
+ public StringBuffer format(long number, StringBuffer dest,
+ FieldPosition fieldPos)
{
- formatInternal (number, new StringFormatBuffer(dest), fieldPos);
+ BigDecimal bigDecimal = new BigDecimal(String.valueOf(number));
+ formatInternal(bigDecimal, true, dest, fieldPos);
return dest;
}
-
- public AttributedCharacterIterator formatToCharacterIterator (Object value)
+
+ /**
+ * Return an <code>AttributedCharacterIterator</code> as a result of
+ * the formatting of the passed {@link Object}.
+ *
+ * @return An {@link AttributedCharacterIterator}.
+ * @throws NullPointerException if value is <code>null</code>.
+ * @throws IllegalArgumentException if value is not an instance of
+ * {@link Number}.
+ */
+ public AttributedCharacterIterator formatToCharacterIterator(Object value)
{
- AttributedFormatBuffer sbuf = new AttributedFormatBuffer();
-
- if (value instanceof Number)
- formatInternal(((Number) value).doubleValue(), sbuf, null);
- else
- throw new IllegalArgumentException
- ("Cannot format given Object as a Number");
+ /*
+ * This method implementation derives directly from the
+ * ICU4J (http://icu.sourceforge.net/) library, distributed under MIT/X.
+ */
- sbuf.sync();
- return new FormatCharacterIterator(sbuf.getBuffer().toString(),
- sbuf.getRanges(),
- sbuf.getAttributes());
- }
-
- public StringBuffer format (long number, StringBuffer dest,
- FieldPosition fieldPos)
- {
- // If using exponential notation, we just format as a double.
- if (useExponentialNotation)
- return format ((double) number, dest, fieldPos);
-
- boolean is_neg = number < 0;
- if (is_neg)
- {
- if (negativePrefix != null)
- dest.append(substituteCurrency(negativePrefix, number));
- else
- {
- dest.append(symbols.getMinusSign());
- dest.append(substituteCurrency(positivePrefix, number));
- }
- number = - number;
- }
- else
- dest.append(substituteCurrency(positivePrefix, number));
-
- int integerBeginIndex = dest.length();
- int index = dest.length();
- int count = 0;
-
- /* Handle percentages, etc. */
- number *= multiplier;
- while (count < maximumIntegerDigits
- && (number > 0 || count < minimumIntegerDigits))
- {
- long dig = number % 10;
- number /= 10;
- // NUMBER and DIG will be less than 0 if the original number
- // was the most negative long.
- if (dig < 0)
- {
- dig = - dig;
- number = - number;
- }
-
- // Append group separator if required.
- if (groupingUsed && count > 0 && groupingSize != 0 && count % groupingSize == 0)
- dest.insert(index, symbols.getGroupingSeparator());
-
- dest.insert(index, (char) (symbols.getZeroDigit() + dig));
+ if (value == null)
+ throw new NullPointerException("Passed Object is null");
+
+ if (!(value instanceof Number)) throw new
+ IllegalArgumentException("Cannot format given Object as a Number");
+
+ StringBuffer text = new StringBuffer();
+ attributes.clear();
+ super.format(value, text, new FieldPosition(0));
- ++count;
- }
+ AttributedString as = new AttributedString(text.toString());
- if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
+ // add NumberFormat field attributes to the AttributedString
+ for (int i = 0; i < attributes.size(); i++)
{
- fieldPos.setBeginIndex(integerBeginIndex);
- fieldPos.setEndIndex(dest.length());
- }
-
- if (decimalSeparatorAlwaysShown || minimumFractionDigits > 0)
- {
- dest.append(symbols.getDecimalSeparator());
- if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
- {
- fieldPos.setBeginIndex(dest.length());
- fieldPos.setEndIndex(dest.length() + minimumFractionDigits);
- }
+ FieldPosition pos = (FieldPosition) attributes.get(i);
+ Format.Field attribute = pos.getFieldAttribute();
+
+ as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos
+ .getEndIndex());
}
-
- for (count = 0; count < minimumFractionDigits; ++count)
- dest.append(symbols.getZeroDigit());
-
- dest.append((is_neg && negativeSuffix != null)
- ? substituteCurrency(negativeSuffix, number)
- : substituteCurrency(positiveSuffix, number));
- return dest;
+
+ // return the CharacterIterator from AttributedString
+ return as.getIterator();
}
-
+
/**
* Returns the currency corresponding to the currency symbol stored
* in the instance of <code>DecimalFormatSymbols</code> used by this
@@ -799,7 +459,7 @@ public class DecimalFormat extends NumberFormat
{
return symbols.getCurrency();
}
-
+
/**
* Returns a copy of the symbols used by this instance.
*
@@ -809,351 +469,326 @@ public class DecimalFormat extends NumberFormat
{
return (DecimalFormatSymbols) symbols.clone();
}
-
- public int getGroupingSize ()
+
+ /**
+ * Gets the interval used between a grouping separator and the next.
+ * For example, a grouping size of 3 means that the number 1234 is
+ * formatted as 1,234.
+ *
+ * The actual character used as grouping separator depends on the
+ * locale and is defined by {@link DecimalFormatSymbols#getDecimalSeparator()}
+ *
+ * @return The interval used between a grouping separator and the next.
+ */
+ public int getGroupingSize()
{
return groupingSize;
}
- public int getMultiplier ()
+ /**
+ * Gets the multiplier used in percent and similar formats.
+ *
+ * @return The multiplier used in percent and similar formats.
+ */
+ public int getMultiplier()
{
return multiplier;
}
-
- public String getNegativePrefix ()
+
+ /**
+ * Gets the negative prefix.
+ *
+ * @return The negative prefix.
+ */
+ public String getNegativePrefix()
{
return negativePrefix;
}
- public String getNegativeSuffix ()
+ /**
+ * Gets the negative suffix.
+ *
+ * @return The negative suffix.
+ */
+ public String getNegativeSuffix()
{
return negativeSuffix;
}
-
- public String getPositivePrefix ()
+
+ /**
+ * Gets the positive prefix.
+ *
+ * @return The positive prefix.
+ */
+ public String getPositivePrefix()
{
return positivePrefix;
}
-
- public String getPositiveSuffix ()
+
+ /**
+ * Gets the positive suffix.
+ *
+ * @return The positive suffix.
+ */
+ public String getPositiveSuffix()
{
return positiveSuffix;
}
-
+
+ public boolean isDecimalSeparatorAlwaysShown()
+ {
+ return decimalSeparatorAlwaysShown;
+ }
+
/**
- * Returns a hash code for this object.
- *
- * @return A hash code.
+ * Define if <code>parse(java.lang.String, java.text.ParsePosition)</code>
+ * should return a {@link BigDecimal} or not.
+ *
+ * @param newValue
*/
- public int hashCode()
+ public void setParseBigDecimal(boolean newValue)
{
- return toPattern().hashCode();
+ this.parseBigDecimal = newValue;
}
-
- public boolean isDecimalSeparatorAlwaysShown ()
+
+ /**
+ * Returns <code>true</code> if
+ * <code>parse(java.lang.String, java.text.ParsePosition)</code> returns
+ * a <code>BigDecimal</code>, <code>false</code> otherwise.
+ * The default return value for this method is <code>false</code>.
+ *
+ * @return <code>true</code> if the parse method returns a {@link BigDecimal},
+ * <code>false</code> otherwise.
+ * @since 1.5
+ * @see #setParseBigDecimal(boolean)
+ */
+ public boolean isParseBigDecimal()
{
- return decimalSeparatorAlwaysShown;
+ return this.parseBigDecimal;
}
-
- public Number parse (String str, ParsePosition pos)
+
+ /**
+ * This method parses the specified string into a <code>Number</code>.
+ *
+ * The parsing starts at <code>pos</code>, which is updated as the parser
+ * consume characters in the passed string.
+ * On error, the <code>Position</code> object index is not updated, while
+ * error position is set appropriately, an <code>null</code> is returned.
+ *
+ * @param str The string to parse.
+ * @param pos The desired <code>ParsePosition</code>.
+ *
+ * @return The parsed <code>Number</code>
+ */
+ public Number parse(String str, ParsePosition pos)
{
- /*
- * Our strategy is simple: copy the text into separate buffers: one for the int part,
- * one for the fraction part and for the exponential part.
- * We translate or omit locale-specific information.
- * If exponential is sufficiently big we merge the fraction and int part and
- * remove the '.' and then we use Long to convert the number. In the other
- * case, we use Double to convert the full number.
- */
-
- boolean is_neg = false;
- int index = pos.getIndex();
- StringBuffer int_buf = new StringBuffer ();
-
- // We have to check both prefixes, because one might be empty. We
- // want to pick the longest prefix that matches.
- boolean got_pos = str.startsWith(positivePrefix, index);
- String np = (negativePrefix != null
- ? negativePrefix
- : positivePrefix + symbols.getMinusSign());
- boolean got_neg = str.startsWith(np, index);
-
- if (got_pos && got_neg)
+ // a special values before anything else
+ // NaN
+ if (str.contains(this.symbols.getNaN()))
+ return Double.valueOf(Double.NaN);
+
+ // this will be our final number
+ StringBuffer number = new StringBuffer();
+
+ // special character
+ char minus = symbols.getMinusSign();
+
+ // starting parsing position
+ int start = pos.getIndex();
+
+ // validate the string, it have to be in the
+ // same form as the format string or parsing will fail
+ String _negativePrefix = (this.negativePrefix.compareTo("") == 0
+ ? minus + positivePrefix
+ : this.negativePrefix);
+
+ // we check both prefixes, because one might be empty.
+ // We want to pick the longest prefix that matches.
+ int positiveLen = positivePrefix.length();
+ int negativeLen = _negativePrefix.length();
+
+ boolean isNegative = str.startsWith(_negativePrefix);
+ boolean isPositive = str.startsWith(positivePrefix);
+
+ if (isPositive && isNegative)
+ {
+ // By checking this way, we preserve ambiguity in the case
+ // where the negative format differs only in suffix.
+ if (negativeLen > positiveLen)
+ {
+ start += _negativePrefix.length();
+ isNegative = true;
+ }
+ else
+ {
+ start += positivePrefix.length();
+ isPositive = true;
+ if (negativeLen < positiveLen)
+ isNegative = false;
+ }
+ }
+ else if (isNegative)
{
- // By checking this way, we preserve ambiguity in the case
- // where the negative format differs only in suffix. We
- // check this again later.
- if (np.length() > positivePrefix.length())
- {
- is_neg = true;
- index += np.length();
- }
- else
- index += positivePrefix.length();
+ start += _negativePrefix.length();
+ isPositive = false;
}
- else if (got_neg)
+ else if (isPositive)
{
- is_neg = true;
- index += np.length();
+ start += positivePrefix.length();
+ isNegative = false;
}
- else if (got_pos)
- index += positivePrefix.length();
else
{
- pos.setErrorIndex (index);
- return null;
+ pos.setErrorIndex(start);
+ return null;
}
-
- // FIXME: handle Inf and NaN.
-
- // FIXME: do we have to respect minimum digits?
- // What about multiplier?
-
- StringBuffer buf = int_buf;
- StringBuffer frac_buf = null;
- StringBuffer exp_buf = null;
- int start_index = index;
- int max = str.length();
- int exp_index = -1;
- int last = index + maximumIntegerDigits;
-
- if (maximumFractionDigits > 0)
- last += maximumFractionDigits + 1;
+
+ // other special characters used by the parser
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char zero = symbols.getZeroDigit();
+ char exponent = symbols.getExponential();
+
+ // stop parsing position in the string
+ int stop = start + this.maximumIntegerDigits + maximumFractionDigits + 2;
if (useExponentialNotation)
- last += minExponentDigits + 1;
-
- if (last > 0 && max > last)
- max = last;
+ stop += minExponentDigits + 1;
+
+ boolean inExponent = false;
- char zero = symbols.getZeroDigit();
- int last_group = -1;
- boolean int_part = true;
- boolean exp_part = false;
- for (; index < max; ++index)
+ // correct the size of the end parsing flag
+ int len = str.length();
+ if (len < stop) stop = len;
+
+ char ch;
+ int i = 0;
+ for (i = start; i < stop; i++)
{
- char c = str.charAt(index);
-
- // FIXME: what about grouping size?
- if (groupingUsed && c == symbols.getGroupingSeparator())
- {
- if (last_group != -1
- && groupingSize != 0
- && (index - last_group) % groupingSize != 0)
- {
- pos.setErrorIndex(index);
- return null;
- }
- last_group = index+1;
- }
- else if (c >= zero && c <= zero + 9)
- {
- buf.append((char) (c - zero + '0'));
- }
- else if (parseIntegerOnly)
- break;
- else if (c == symbols.getDecimalSeparator())
- {
- if (last_group != -1
- && groupingSize != 0
- && (index - last_group) % groupingSize != 0)
- {
- pos.setErrorIndex(index);
- return null;
- }
- buf = frac_buf = new StringBuffer();
- frac_buf.append('.');
- int_part = false;
- }
- else if (c == symbols.getExponential())
- {
- buf = exp_buf = new StringBuffer();
- int_part = false;
- exp_part = true;
- exp_index = index+1;
- }
- else if (exp_part
- && (c == '+' || c == '-' || c == symbols.getMinusSign()))
- {
- // For exponential notation.
- buf.append(c);
- }
- else
- break;
+ ch = str.charAt(i);
+
+ if (ch >= zero && ch <= (zero + 9))
+ {
+ number.append(ch);
+ }
+ else if (this.parseIntegerOnly)
+ {
+ break;
+ }
+ else if (ch == decimalSeparator)
+ {
+ number.append('.');
+ }
+ else if (ch == exponent)
+ {
+ number.append(ch);
+ inExponent = !inExponent;
+ }
+ else if ((ch == '+' || ch == '-' || ch == minus))
+ {
+ if (inExponent)
+ number.append(ch);
+ else
+ break;
+ }
}
- if (index == start_index)
+ // 2nd special case: infinity
+ // XXX: need to be tested
+ if (str.contains(symbols.getInfinity()))
{
- // Didn't see any digits.
- pos.setErrorIndex(index);
- return null;
- }
+ int inf = str.indexOf(symbols.getInfinity());
+ pos.setIndex(inf);
+
+ // FIXME: ouch, this is really ugly and lazy code...
+ if (this.parseBigDecimal)
+ {
+ if (isNegative)
+ return new BigDecimal(Double.NEGATIVE_INFINITY);
+
+ return new BigDecimal(Double.POSITIVE_INFINITY);
+ }
+
+ if (isNegative)
+ return new Double(Double.NEGATIVE_INFINITY);
- // Check the suffix. We must do this before converting the
- // buffer to a number to handle the case of a number which is
- // the most negative Long.
- boolean got_pos_suf = str.startsWith(positiveSuffix, index);
- String ns = (negativePrefix == null ? positiveSuffix : negativeSuffix);
- boolean got_neg_suf = str.startsWith(ns, index);
- if (is_neg)
- {
- if (! got_neg_suf)
- {
- pos.setErrorIndex(index);
- return null;
- }
- }
- else if (got_pos && got_neg && got_neg_suf)
- {
- is_neg = true;
- }
- else if (got_pos != got_pos_suf && got_neg != got_neg_suf)
- {
- pos.setErrorIndex(index);
- return null;
+ return new Double(Double.POSITIVE_INFINITY);
}
- else if (! got_pos_suf)
+
+ // no number...
+ if (i == start || number.length() == 0)
{
- pos.setErrorIndex(index);
- return null;
+ pos.setErrorIndex(i);
+ return null;
}
- String suffix = is_neg ? ns : positiveSuffix;
- long parsedMultiplier = 1;
- boolean use_long;
-
- if (is_neg)
- int_buf.insert(0, '-');
+ // now we have to check the suffix, done here after number parsing
+ // or the index will not be updated correctly...
+ boolean isNegativeSuffix = str.endsWith(this.negativeSuffix);
+ boolean isPositiveSuffix = str.endsWith(this.positiveSuffix);
+ boolean positiveEqualsNegative = negativeSuffix.equals(positiveSuffix);
- // Now handle the exponential part if there is one.
- if (exp_buf != null)
+ positiveLen = positiveSuffix.length();
+ negativeLen = negativeSuffix.length();
+
+ if (isNegative && !isNegativeSuffix)
{
- int exponent_value;
-
- try
- {
- exponent_value = Integer.parseInt(exp_buf.toString());
- }
- catch (NumberFormatException x1)
- {
- pos.setErrorIndex(exp_index);
- return null;
- }
-
- if (frac_buf == null)
- {
- // We only have to add some zeros to the int part.
- // Build a multiplier.
- for (int i = 0; i < exponent_value; i++)
- int_buf.append('0');
-
- use_long = true;
- }
- else
- {
- boolean long_sufficient;
-
- if (exponent_value < frac_buf.length()-1)
- {
- int lastNonNull = -1;
- /* We have to check the fraction buffer: it may only be full of '0'
- * or be sufficiently filled with it to convert the number into Long.
- */
- for (int i = 1; i < frac_buf.length(); i++)
- if (frac_buf.charAt(i) != '0')
- lastNonNull = i;
-
- long_sufficient = (lastNonNull < 0 || lastNonNull <= exponent_value);
- }
- else
- long_sufficient = true;
-
- if (long_sufficient)
- {
- for (int i = 1; i < frac_buf.length() && i < exponent_value; i++)
- int_buf.append(frac_buf.charAt(i));
- for (int i = frac_buf.length()-1; i < exponent_value; i++)
- int_buf.append('0');
- use_long = true;
- }
- else
- {
- /*
- * A long type is not sufficient, we build the full buffer to
- * be parsed by Double.
- */
- int_buf.append(frac_buf);
- int_buf.append('E');
- int_buf.append(exp_buf);
- use_long = false;
- }
- }
+ pos.setErrorIndex(i);
+ return null;
}
- else
+ else if (isNegativeSuffix &&
+ !positiveEqualsNegative &&
+ (negativeLen > positiveLen))
{
- if (frac_buf != null)
- {
- /* Check whether the fraction buffer contains only '0' */
- int i;
- for (i = 1; i < frac_buf.length(); i++)
- if (frac_buf.charAt(i) != '0')
- break;
-
- if (i != frac_buf.length())
- {
- use_long = false;
- int_buf.append(frac_buf);
- }
- else
- use_long = true;
- }
- else
- use_long = true;
+ isNegative = true;
}
-
- String t = int_buf.toString();
- Number result = null;
- if (use_long)
+ else if (!isPositiveSuffix)
{
- try
- {
- result = new Long (t);
- }
- catch (NumberFormatException x1)
- {
- }
+ pos.setErrorIndex(i);
+ return null;
}
- else
+
+ if (isNegative) number.insert(0, '-');
+
+ pos.setIndex(i);
+
+ // now we handle the return type
+ BigDecimal bigDecimal = new BigDecimal(number.toString());
+ if (this.parseBigDecimal)
+ return bigDecimal;
+
+ // want integer?
+ if (this.parseIntegerOnly)
+ return new Long(bigDecimal.longValue());
+
+ // 3th special case -0.0
+ if (isNegative && (bigDecimal.compareTo(BigDecimal.ZERO) == 0))
+ return new Double(-0.0);
+
+ try
{
- try
- {
- result = new Double (t);
- }
- catch (NumberFormatException x2)
- {
- }
+ BigDecimal integer
+ = bigDecimal.setScale(0, BigDecimal.ROUND_UNNECESSARY);
+ return new Long(integer.longValue());
}
- if (result == null)
+ catch (ArithmeticException e)
{
- pos.setErrorIndex(index);
- return null;
+ return new Double(bigDecimal.doubleValue());
}
-
- pos.setIndex(index + suffix.length());
-
- return result;
}
/**
* Sets the <code>Currency</code> on the
* <code>DecimalFormatSymbols</code> used, which also sets the
* currency symbols on those symbols.
+ *
+ * @param currency The new <code>Currency</code> on the
+ * <code>DecimalFormatSymbols</code>.
*/
public void setCurrency(Currency currency)
{
symbols.setCurrency(currency);
}
-
+
/**
* Sets the symbols used by this instance. This method makes a copy of
* the supplied symbols.
@@ -1164,274 +799,1412 @@ public class DecimalFormat extends NumberFormat
{
symbols = (DecimalFormatSymbols) newSymbols.clone();
}
-
- public void setDecimalSeparatorAlwaysShown (boolean newValue)
+
+ /**
+ * Define if the decimal separator should be always visible or only
+ * visible when needed. This method as effect only on integer values.
+ * Pass <code>true</code> if you want the decimal separator to be
+ * always shown, <code>false</code> otherwise.
+ *
+ * @param newValue true</code> if you want the decimal separator to be
+ * always shown, <code>false</code> otherwise.
+ */
+ public void setDecimalSeparatorAlwaysShown(boolean newValue)
{
decimalSeparatorAlwaysShown = newValue;
}
-
- public void setGroupingSize (int groupSize)
+
+ /**
+ * Sets the number of digits used to group portions of the integer part of
+ * the number. For example, the number <code>123456</code>, with a grouping
+ * size of 3, is rendered <code>123,456</code>.
+ *
+ * @param groupSize The number of digits used while grouping portions
+ * of the integer part of a number.
+ */
+ public void setGroupingSize(int groupSize)
{
groupingSize = (byte) groupSize;
}
-
- public void setMaximumFractionDigits (int newValue)
+
+ /**
+ * Sets the maximum number of digits allowed in the integer
+ * portion of a number to the specified value.
+ * The new value will be the choosen as the minimum between
+ * <code>newvalue</code> and 309. Any value below zero will be
+ * replaced by zero.
+ *
+ * @param newValue The new maximum integer digits value.
+ */
+ public void setMaximumIntegerDigits(int newValue)
{
- super.setMaximumFractionDigits(Math.min(newValue, 340));
+ newValue = (newValue > 0) ? newValue : 0;
+ super.setMaximumIntegerDigits(Math.min(newValue, DEFAULT_INTEGER_DIGITS));
}
-
- public void setMaximumIntegerDigits (int newValue)
+
+ /**
+ * Sets the minimum number of digits allowed in the integer
+ * portion of a number to the specified value.
+ * The new value will be the choosen as the minimum between
+ * <code>newvalue</code> and 309. Any value below zero will be
+ * replaced by zero.
+ *
+ * @param newValue The new minimum integer digits value.
+ */
+ public void setMinimumIntegerDigits(int newValue)
{
- super.setMaximumIntegerDigits(Math.min(newValue, 309));
+ newValue = (newValue > 0) ? newValue : 0;
+ super.setMinimumIntegerDigits(Math.min(newValue, DEFAULT_INTEGER_DIGITS));
}
-
- public void setMinimumFractionDigits (int newValue)
+
+ /**
+ * Sets the maximum number of digits allowed in the fraction
+ * portion of a number to the specified value.
+ * The new value will be the choosen as the minimum between
+ * <code>newvalue</code> and 309. Any value below zero will be
+ * replaced by zero.
+ *
+ * @param newValue The new maximum fraction digits value.
+ */
+ public void setMaximumFractionDigits(int newValue)
{
- super.setMinimumFractionDigits(Math.min(newValue, 340));
+ newValue = (newValue > 0) ? newValue : 0;
+ super.setMaximumFractionDigits(Math.min(newValue, DEFAULT_FRACTION_DIGITS));
}
-
- public void setMinimumIntegerDigits (int newValue)
+
+ /**
+ * Sets the minimum number of digits allowed in the fraction
+ * portion of a number to the specified value.
+ * The new value will be the choosen as the minimum between
+ * <code>newvalue</code> and 309. Any value below zero will be
+ * replaced by zero.
+ *
+ * @param newValue The new minimum fraction digits value.
+ */
+ public void setMinimumFractionDigits(int newValue)
{
- super.setMinimumIntegerDigits(Math.min(newValue, 309));
+ newValue = (newValue > 0) ? newValue : 0;
+ super.setMinimumFractionDigits(Math.min(newValue, DEFAULT_FRACTION_DIGITS));
}
-
- public void setMultiplier (int newValue)
+
+ /**
+ * Sets the multiplier for use in percent and similar formats.
+ * For example, for percent set the multiplier to 100, for permille, set the
+ * miltiplier to 1000.
+ *
+ * @param newValue the new value for multiplier.
+ */
+ public void setMultiplier(int newValue)
{
multiplier = newValue;
}
-
- public void setNegativePrefix (String newValue)
+
+ /**
+ * Sets the negative prefix.
+ *
+ * @param newValue The new negative prefix.
+ */
+ public void setNegativePrefix(String newValue)
{
negativePrefix = newValue;
}
- public void setNegativeSuffix (String newValue)
+ /**
+ * Sets the negative suffix.
+ *
+ * @param newValue The new negative suffix.
+ */
+ public void setNegativeSuffix(String newValue)
{
negativeSuffix = newValue;
}
-
- public void setPositivePrefix (String newValue)
+
+ /**
+ * Sets the positive prefix.
+ *
+ * @param newValue The new positive prefix.
+ */
+ public void setPositivePrefix(String newValue)
{
positivePrefix = newValue;
}
-
- public void setPositiveSuffix (String newValue)
+
+ /**
+ * Sets the new positive suffix.
+ *
+ * @param newValue The new positive suffix.
+ */
+ public void setPositiveSuffix(String newValue)
{
positiveSuffix = newValue;
}
+
+ /**
+ * This method returns a string with the formatting pattern being used
+ * by this object. The string is localized.
+ *
+ * @return A localized <code>String</code> with the formatting pattern.
+ * @see #toPattern()
+ */
+ public String toLocalizedPattern()
+ {
+ return computePattern(this.symbols);
+ }
+
+ /**
+ * This method returns a string with the formatting pattern being used
+ * by this object. The string is not localized.
+ *
+ * @return A <code>String</code> with the formatting pattern.
+ * @see #toLocalizedPattern()
+ */
+ public String toPattern()
+ {
+ return computePattern(nonLocalizedSymbols);
+ }
+
+ /* ***** private methods ***** */
+
+ /**
+ * This is an shortcut helper method used to test if two given strings are
+ * equals.
+ *
+ * @param s1 The first string to test for equality.
+ * @param s2 The second string to test for equality.
+ * @return <code>true</code> if the strings are both <code>null</code> or
+ * equals.
+ */
+ private boolean equals(String s1, String s2)
+ {
+ if (s1 == null || s2 == null)
+ return s1 == s2;
+ return s1.equals(s2);
+ }
+
+
+ /* ****** PATTERN ****** */
+
+ /**
+ * This helper function creates a string consisting of all the
+ * characters which can appear in a pattern and must be quoted.
+ */
+ private String patternChars (DecimalFormatSymbols syms)
+ {
+ StringBuffer buf = new StringBuffer ();
+
+ buf.append(syms.getDecimalSeparator());
+ buf.append(syms.getDigit());
+ buf.append(syms.getExponential());
+ buf.append(syms.getGroupingSeparator());
+ buf.append(syms.getMinusSign());
+ buf.append(syms.getPatternSeparator());
+ buf.append(syms.getPercent());
+ buf.append(syms.getPerMill());
+ buf.append(syms.getZeroDigit());
+ buf.append('\'');
+ buf.append('\u00a4');
+
+ return buf.toString();
+ }
- private void quoteFix(StringBuffer buf, String text, String patChars)
+ /**
+ * Quote special characters as defined by <code>patChars</code> in the
+ * input string.
+ *
+ * @param text
+ * @param patChars
+ * @return A StringBuffer with special characters quoted.
+ */
+ private StringBuffer quoteFix(String text, String patChars)
{
+ StringBuffer buf = new StringBuffer();
+
int len = text.length();
+ char ch;
for (int index = 0; index < len; ++index)
{
- char c = text.charAt(index);
- if (patChars.indexOf(c) != -1)
- {
- buf.append('\'');
- buf.append(c);
- buf.append('\'');
- }
- else
- buf.append(c);
+ ch = text.charAt(index);
+ if (patChars.indexOf(ch) != -1)
+ {
+ buf.append('\'');
+ buf.append(ch);
+ if (ch != '\'') buf.append('\'');
+ }
+ else
+ {
+ buf.append(ch);
+ }
}
+
+ return buf;
}
-
- private String computePattern(DecimalFormatSymbols syms)
+
+ /**
+ * Returns the format pattern, localized to follow the given
+ * symbols.
+ */
+ private String computePattern(DecimalFormatSymbols symbols)
{
- StringBuffer mainPattern = new StringBuffer ();
+ StringBuffer mainPattern = new StringBuffer();
+
// We have to at least emit a zero for the minimum number of
- // digits. Past that we need hash marks up to the grouping
+ // digits. Past that we need hash marks up to the grouping
// separator (and one beyond).
- int total_digits = Math.max(minimumIntegerDigits,
- groupingUsed ? groupingSize + 1: groupingSize);
- for (int i = 0; i < total_digits - minimumIntegerDigits; ++i)
- mainPattern.append(syms.getDigit());
- for (int i = total_digits - minimumIntegerDigits; i < total_digits; ++i)
- mainPattern.append(syms.getZeroDigit());
- // Inserting the gropuing operator afterwards is easier.
+ int _groupingSize = groupingUsed ? groupingSize + 1: groupingSize;
+ int totalDigits = Math.max(minimumIntegerDigits, _groupingSize);
+
+ // if it is not in exponential notiation,
+ // we always have a # prebended
+ if (!useExponentialNotation) mainPattern.append(symbols.getDigit());
+
+ for (int i = 1; i < totalDigits - minimumIntegerDigits; i++)
+ mainPattern.append(symbols.getDigit());
+
+ for (int i = totalDigits - minimumIntegerDigits; i < totalDigits; i++)
+ mainPattern.append(symbols.getZeroDigit());
+
if (groupingUsed)
- mainPattern.insert(mainPattern.length() - groupingSize,
- syms.getGroupingSeparator());
+ {
+ mainPattern.insert(mainPattern.length() - groupingSize,
+ symbols.getGroupingSeparator());
+ }
+
// See if we need decimal info.
- if (minimumFractionDigits > 0 || maximumFractionDigits > 0
- || decimalSeparatorAlwaysShown)
- mainPattern.append(syms.getDecimalSeparator());
+ if (minimumFractionDigits > 0 || maximumFractionDigits > 0 ||
+ decimalSeparatorAlwaysShown)
+ {
+ mainPattern.append(symbols.getDecimalSeparator());
+ }
+
for (int i = 0; i < minimumFractionDigits; ++i)
- mainPattern.append(syms.getZeroDigit());
+ mainPattern.append(symbols.getZeroDigit());
+
for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i)
- mainPattern.append(syms.getDigit());
+ mainPattern.append(symbols.getDigit());
+
if (useExponentialNotation)
{
- mainPattern.append(syms.getExponential());
- for (int i = 0; i < minExponentDigits; ++i)
- mainPattern.append(syms.getZeroDigit());
- if (minExponentDigits == 0)
- mainPattern.append(syms.getDigit());
+ mainPattern.append(symbols.getExponential());
+
+ for (int i = 0; i < minExponentDigits; ++i)
+ mainPattern.append(symbols.getZeroDigit());
+
+ if (minExponentDigits == 0)
+ mainPattern.append(symbols.getDigit());
}
-
- String main = mainPattern.toString();
- String patChars = patternChars (syms);
- mainPattern.setLength(0);
-
- quoteFix (mainPattern, positivePrefix, patChars);
- mainPattern.append(main);
- quoteFix (mainPattern, positiveSuffix, patChars);
-
- if (negativePrefix != null)
+
+ // save the pattern
+ String pattern = mainPattern.toString();
+
+ // so far we have the pattern itself, now we need to add
+ // the positive and the optional negative prefixes and suffixes
+ String patternChars = patternChars(symbols);
+ mainPattern.insert(0, quoteFix(positivePrefix, patternChars));
+ mainPattern.append(quoteFix(positiveSuffix, patternChars));
+
+ if (hasNegativePrefix)
{
- quoteFix (mainPattern, negativePrefix, patChars);
- mainPattern.append(main);
- quoteFix (mainPattern, negativeSuffix, patChars);
+ mainPattern.append(symbols.getPatternSeparator());
+ mainPattern.append(quoteFix(negativePrefix, patternChars));
+ mainPattern.append(pattern);
+ mainPattern.append(quoteFix(negativeSuffix, patternChars));
}
-
+
+ // finally, return the pattern string
return mainPattern.toString();
}
-
- public String toLocalizedPattern ()
+
+ /* ****** FORMAT PARSING ****** */
+
+ /**
+ * Scan the input string and define a pattern suitable for use
+ * with this decimal format.
+ *
+ * @param pattern
+ * @param symbols
+ */
+ private void applyPatternWithSymbols(String pattern,
+ DecimalFormatSymbols symbols)
{
- return computePattern (symbols);
+ // The pattern string is described by a BNF diagram.
+ // we could use a recursive parser to read and prepare
+ // the string, but this would be too slow and resource
+ // intensive, while this code is quite critical as it is
+ // called always when the class is instantiated and every
+ // time a new pattern is given.
+ // Our strategy is to divide the string into section as given by
+ // the BNF diagram, iterating through the string and setting up
+ // the parameters we need for formatting (which is basicly what
+ // a descendent recursive parser would do - but without recursion).
+ // I'm sure that there are smarter methods to do this.
+
+ // Restore default values. Most of these will be overwritten
+ // but we want to be sure that nothing is left out.
+ setDefaultValues();
+
+ int len = pattern.length();
+ if (len == 0)
+ {
+ // this is another special case...
+ this.minimumIntegerDigits = 1;
+ this.maximumIntegerDigits = DEFAULT_INTEGER_DIGITS;
+ this.minimumFractionDigits = 0;
+ this.maximumFractionDigits = DEFAULT_FRACTION_DIGITS;
+
+ // FIXME: ...and these values may not be valid in all locales
+ this.minExponentDigits = 0;
+ this.showDecimalSeparator = true;
+ this.groupingUsed = true;
+ this.groupingSize = 3;
+
+ return;
+ }
+
+ int start = scanFix(pattern, symbols, 0, true);
+ if (start < len) start = scanNumberInteger(pattern, symbols, start);
+ if (start < len)
+ {
+ start = scanFractionalPortion(pattern, symbols, start);
+ }
+ else
+ {
+ // special case, pattern that ends here does not have a fractional
+ // portion
+ this.minimumFractionDigits = 0;
+ this.maximumFractionDigits = 0;
+ //this.decimalSeparatorAlwaysShown = false;
+ //this.showDecimalSeparator = false;
+ }
+
+ // XXX: this fixes a compatibility test with the RI.
+ // If new uses cases fail, try removing this line first.
+ //if (!this.hasIntegerPattern && !this.hasFractionalPattern)
+ // throw new IllegalArgumentException("No valid pattern found!");
+
+ if (start < len) start = scanExponent(pattern, symbols, start);
+ if (start < len) start = scanFix(pattern, symbols, start, false);
+ if (start < len) scanNegativePattern(pattern, symbols, start);
+
+ if (useExponentialNotation &&
+ (maxIntegerDigitsExponent > minimumIntegerDigits) &&
+ (maxIntegerDigitsExponent > 1))
+ {
+ minimumIntegerDigits = 1;
+ exponentRound = maxIntegerDigitsExponent;
+ }
+
+ if (useExponentialNotation)
+ maximumIntegerDigits = maxIntegerDigitsExponent;
+
+ if (!this.hasFractionalPattern && this.showDecimalSeparator == true)
+ {
+ this.decimalSeparatorAlwaysShown = true;
+ }
}
+
+ /**
+ * Scans for the prefix or suffix portion of the pattern string.
+ * This method handles the positive subpattern of the pattern string.
+ *
+ * @param pattern The pattern string to parse.
+ * @return The position in the pattern string where parsing ended.
+ */
+ private int scanFix(String pattern, DecimalFormatSymbols sourceSymbols,
+ int start, boolean prefix)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ // the number portion is always delimited by one of those
+ // characters
+ char decimalSeparator = sourceSymbols.getDecimalSeparator();
+ char patternSeparator = sourceSymbols.getPatternSeparator();
+ char groupingSeparator = sourceSymbols.getGroupingSeparator();
+ char digit = sourceSymbols.getDigit();
+ char zero = sourceSymbols.getZeroDigit();
+ char minus = sourceSymbols.getMinusSign();
+
+ // other special characters, cached here to avoid method calls later
+ char percent = sourceSymbols.getPercent();
+ char permille = sourceSymbols.getPerMill();
+
+ String currencySymbol = this.symbols.getCurrencySymbol();
+
+ boolean quote = false;
+
+ char ch = pattern.charAt(start);
+ if (ch == patternSeparator)
+ {
+ // negative subpattern
+ this.hasNegativePrefix = true;
+ ++start;
+ return start;
+ }
+
+ int len = pattern.length();
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ // we are entering into the negative subpattern
+ if (!quote && ch == patternSeparator)
+ {
+ if (this.hasNegativePrefix)
+ {
+ throw new IllegalArgumentException("Invalid pattern found: "
+ + start);
+ }
+
+ this.hasNegativePrefix = true;
+ ++i;
+ break;
+ }
+
+ // this means we are inside the number portion
+ if (!quote &&
+ (ch == minus || ch == digit || ch == zero ||
+ ch == groupingSeparator))
+ break;
+
+ if (!quote && ch == decimalSeparator)
+ {
+ this.showDecimalSeparator = true;
+ break;
+ }
+ else if (quote && ch != '\'')
+ {
+ buffer.append(ch);
+ continue;
+ }
+
+ if (ch == '\u00A4')
+ {
+ // CURRENCY
+ currencySymbol = this.symbols.getCurrencySymbol();
+
+ // if \u00A4 is doubled, we use the international currency symbol
+ if (i < len && pattern.charAt(i + 1) == '\u00A4')
+ {
+ currencySymbol = this.symbols.getInternationalCurrencySymbol();
+ i++;
+ }
+
+ this.useCurrencySeparator = true;
+ buffer.append(currencySymbol);
+ }
+ else if (ch == percent)
+ {
+ // PERCENT
+ this.multiplier = 100;
+ buffer.append(this.symbols.getPercent());
+ }
+ else if (ch == permille)
+ {
+ // PERMILLE
+ this.multiplier = 1000;
+ buffer.append(this.symbols.getPerMill());
+ }
+ else if (ch == '\'')
+ {
+ // QUOTE
+ if (i < len && pattern.charAt(i + 1) == '\'')
+ {
+ // we need to add ' to the buffer
+ buffer.append(ch);
+ i++;
+ }
+ else
+ {
+ quote = !quote;
+ continue;
+ }
+ }
+ else
+ {
+ buffer.append(ch);
+ }
+ }
+
+ if (prefix)
+ {
+ this.positivePrefix = buffer.toString();
+ this.negativePrefix = minus + "" + positivePrefix;
+ }
+ else
+ {
+ this.positiveSuffix = buffer.toString();
+ }
+
+ return i;
+ }
+
+ /**
+ * Scan the given string for number patterns, starting
+ * from <code>start</code>.
+ * This method searches the integer part of the pattern only.
+ *
+ * @param pattern The pattern string to parse.
+ * @param start The starting parse position in the string.
+ * @return The position in the pattern string where parsing ended,
+ * counted from the beginning of the string (that is, 0).
+ */
+ private int scanNumberInteger(String pattern, DecimalFormatSymbols symbols,
+ int start)
+ {
+ char digit = symbols.getDigit();
+ char zero = symbols.getZeroDigit();
+ char groupingSeparator = symbols.getGroupingSeparator();
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char exponent = symbols.getExponential();
+ char patternSeparator = symbols.getPatternSeparator();
+
+ // count the number of zeroes in the pattern
+ // this number defines the minum digits in the integer portion
+ int zeros = 0;
+
+ // count the number of digits used in grouping
+ int _groupingSize = 0;
+
+ this.maxIntegerDigitsExponent = 0;
+
+ boolean intPartTouched = false;
+
+ char ch;
+ int len = pattern.length();
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ // break on decimal separator or exponent or pattern separator
+ if (ch == decimalSeparator || ch == exponent)
+ break;
+
+ if (this.hasNegativePrefix && ch == patternSeparator)
+ throw new IllegalArgumentException("Invalid pattern found: "
+ + start);
+
+ if (ch == digit)
+ {
+ // in our implementation we could relax this strict
+ // requirement, but this is used to keep compatibility with
+ // the RI
+ if (zeros > 0) throw new
+ IllegalArgumentException("digit mark following zero in " +
+ "positive subpattern, not allowed. Position: " + i);
+
+ _groupingSize++;
+ intPartTouched = true;
+ this.maxIntegerDigitsExponent++;
+ }
+ else if (ch == zero)
+ {
+ zeros++;
+ _groupingSize++;
+ this.maxIntegerDigitsExponent++;
+ }
+ else if (ch == groupingSeparator)
+ {
+ this.groupingSeparatorInPattern = true;
+ this.groupingUsed = true;
+ _groupingSize = 0;
+ }
+ else
+ {
+ // any other character not listed above
+ // means we are in the suffix portion
+ break;
+ }
+ }
+
+ if (groupingSeparatorInPattern) this.groupingSize = (byte) _groupingSize;
+ this.minimumIntegerDigits = zeros;
+
+ // XXX: compatibility code with the RI: the number of minimum integer
+ // digits is at least one when maximumIntegerDigits is more than zero
+ if (intPartTouched && this.maximumIntegerDigits > 0 &&
+ this.minimumIntegerDigits == 0)
+ this.minimumIntegerDigits = 1;
- public String toPattern ()
+ return i;
+ }
+
+ /**
+ * Scan the given string for number patterns, starting
+ * from <code>start</code>.
+ * This method searches the fractional part of the pattern only.
+ *
+ * @param pattern The pattern string to parse.
+ * @param start The starting parse position in the string.
+ * @return The position in the pattern string where parsing ended,
+ * counted from the beginning of the string (that is, 0).
+ */
+ private int scanFractionalPortion(String pattern,
+ DecimalFormatSymbols symbols,
+ int start)
+ {
+ char digit = symbols.getDigit();
+ char zero = symbols.getZeroDigit();
+ char groupingSeparator = symbols.getGroupingSeparator();
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char exponent = symbols.getExponential();
+ char patternSeparator = symbols.getPatternSeparator();
+
+ // first character needs to be '.' otherwise we are not parsing the
+ // fractional portion
+ char ch = pattern.charAt(start);
+ if (ch != decimalSeparator)
+ {
+ this.minimumFractionDigits = 0;
+ this.maximumFractionDigits = 0;
+ return start;
+ }
+
+ ++start;
+
+ this.hasFractionalPattern = true;
+
+ this.minimumFractionDigits = 0;
+ int digits = 0;
+
+ int len = pattern.length();
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ // we hit the exponential or negative subpattern
+ if (ch == exponent || ch == patternSeparator)
+ break;
+
+ // pattern error
+ if (ch == groupingSeparator || ch == decimalSeparator) throw new
+ IllegalArgumentException("unexpected character '" + ch + "' " +
+ "in fractional subpattern. Position: " + i);
+
+ if (ch == digit)
+ {
+ digits++;
+ }
+ else if (ch == zero)
+ {
+ if (digits > 0) throw new
+ IllegalArgumentException("digit mark following zero in " +
+ "positive subpattern, not allowed. Position: " + i);
+
+ this.minimumFractionDigits++;
+ }
+ else
+ {
+ // we are in the suffix section of pattern
+ break;
+ }
+ }
+
+ if (i == start) this.hasFractionalPattern = false;
+
+ this.maximumFractionDigits = this.minimumFractionDigits + digits;
+ this.showDecimalSeparator = true;
+
+ return i;
+ }
+
+ /**
+ * Scan the given string for number patterns, starting
+ * from <code>start</code>.
+ * This method searches the expoential part of the pattern only.
+ *
+ * @param pattern The pattern string to parse.
+ * @param start The starting parse position in the string.
+ * @return The position in the pattern string where parsing ended,
+ * counted from the beginning of the string (that is, 0).
+ */
+ private int scanExponent(String pattern, DecimalFormatSymbols symbols,
+ int start)
+ {
+ char digit = symbols.getDigit();
+ char zero = symbols.getZeroDigit();
+ char groupingSeparator = symbols.getGroupingSeparator();
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char exponent = symbols.getExponential();
+
+ char ch = pattern.charAt(start);
+
+ if (ch == decimalSeparator)
+ {
+ // ignore dots
+ ++start;
+ }
+
+ if (ch != exponent)
+ {
+ this.useExponentialNotation = false;
+ return start;
+ }
+
+ ++start;
+
+ this.minExponentDigits = 0;
+
+ int len = pattern.length();
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ if (ch == groupingSeparator || ch == decimalSeparator ||
+ ch == digit || ch == exponent) throw new
+ IllegalArgumentException("unexpected character '" + ch + "' " +
+ "in exponential subpattern. Position: " + i);
+
+ if (ch == zero)
+ {
+ this.minExponentDigits++;
+ }
+ else
+ {
+ // any character other than zero is an exit point
+ break;
+ }
+ }
+
+ this.useExponentialNotation = true;
+
+ return i;
+ }
+
+ /**
+ * Scan the given string for number patterns, starting
+ * from <code>start</code>.
+ * This method searches the negative part of the pattern only and scan
+ * throught the end of the string.
+ *
+ * @param pattern The pattern string to parse.
+ * @param start The starting parse position in the string.
+ */
+ private void scanNegativePattern(String pattern,
+ DecimalFormatSymbols sourceSymbols,
+ int start)
{
- return computePattern (nonLocalizedSymbols);
+ StringBuffer buffer = new StringBuffer();
+
+ // the number portion is always delimited by one of those
+ // characters
+ char decimalSeparator = sourceSymbols.getDecimalSeparator();
+ char patternSeparator = sourceSymbols.getPatternSeparator();
+ char groupingSeparator = sourceSymbols.getGroupingSeparator();
+ char digit = sourceSymbols.getDigit();
+ char zero = sourceSymbols.getZeroDigit();
+ char minus = sourceSymbols.getMinusSign();
+
+ // other special charcaters, cached here to avoid method calls later
+ char percent = sourceSymbols.getPercent();
+ char permille = sourceSymbols.getPerMill();
+
+ String CURRENCY_SYMBOL = this.symbols.getCurrencySymbol();
+ String currencySymbol = CURRENCY_SYMBOL;
+
+ boolean quote = false;
+ boolean prefixDone = false;
+
+ int len = pattern.length();
+ if (len > 0) this.hasNegativePrefix = true;
+
+ char ch = pattern.charAt(start);
+ if (ch == patternSeparator)
+ {
+ // no pattern separator in the negative pattern
+ if ((start + 1) > len) throw new
+ IllegalArgumentException("unexpected character '" + ch + "' " +
+ "in negative subpattern.");
+ start++;
+ }
+
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ // this means we are inside the number portion
+ if (!quote &&
+ (ch == digit || ch == zero || ch == decimalSeparator ||
+ ch == patternSeparator || ch == groupingSeparator))
+ {
+ if (!prefixDone)
+ {
+ this.negativePrefix = buffer.toString();
+ buffer.delete(0, buffer.length());
+ prefixDone = true;
+ }
+ }
+ else if (ch == minus)
+ {
+ buffer.append(this.symbols.getMinusSign());
+ }
+ else if (quote && ch != '\'')
+ {
+ buffer.append(ch);
+ }
+ else if (ch == '\u00A4')
+ {
+ // CURRENCY
+ currencySymbol = CURRENCY_SYMBOL;
+
+ // if \u00A4 is doubled, we use the international currency symbol
+ if ((i + 1) < len && pattern.charAt(i + 1) == '\u00A4')
+ {
+ currencySymbol = this.symbols.getInternationalCurrencySymbol();
+ i = i + 2;
+ }
+
+ // FIXME: not sure about this, the specs says that we only have to
+ // change prefix and suffix, so leave it as commented
+ // unless in case of bug report/errors
+ //this.useCurrencySeparator = true;
+
+ buffer.append(currencySymbol);
+ }
+ else if (ch == percent)
+ {
+ // PERCENT
+ this.negativePatternMultiplier = 100;
+ buffer.append(this.symbols.getPercent());
+ }
+ else if (ch == permille)
+ {
+ // PERMILLE
+ this.negativePatternMultiplier = 1000;
+ buffer.append(this.symbols.getPerMill());
+ }
+ else if (ch == '\'')
+ {
+ // QUOTE
+ if (i < len && pattern.charAt(i + 1) == '\'')
+ {
+ // we need to add ' to the buffer
+ buffer.append(ch);
+ i++;
+ }
+ else
+ {
+ quote = !quote;
+ }
+ }
+ else if (ch == patternSeparator)
+ {
+ // no pattern separator in the negative pattern
+ throw new IllegalArgumentException("unexpected character '" + ch +
+ "' in negative subpattern.");
+ }
+ else
+ {
+ buffer.append(ch);
+ }
+ }
+
+ if (prefixDone)
+ this.negativeSuffix = buffer.toString();
+ else
+ this.negativePrefix = buffer.toString();
}
+
+ /* ****** FORMATTING ****** */
+
+ /**
+ * Handles the real formatting.
+ *
+ * We use a BigDecimal to format the number without precision loss.
+ * All the rounding is done by methods in BigDecimal.
+ * The <code>isLong</code> parameter is used to determine if we are
+ * formatting a long or BigInteger. In this case, we avoid to format
+ * the fractional part of the number (unless specified otherwise in the
+ * format string) that would consist only of a 0 digit.
+ *
+ * @param number A BigDecimal representation fo the input number.
+ * @param dest The destination buffer.
+ * @param isLong A boolean that indicates if this BigDecimal is a real
+ * decimal or an integer.
+ * @param fieldPos Use to keep track of the formatting position.
+ */
+ private void formatInternal(BigDecimal number, boolean isLong,
+ StringBuffer dest, FieldPosition fieldPos)
+ {
+ // The specs says that fieldPos should not be null, and that we
+ // should throw a NPE, but it seems that in few classes that
+ // reference this one, fieldPos is set to null.
+ // This is even defined in the javadoc, see for example MessageFormat.
+ // I think the best here is to check for fieldPos and build one if it is
+ // null. If it cause harms or regressions, just remove this line and
+ // fix the classes in the point of call, insted.
+ if (fieldPos == null) fieldPos = new FieldPosition(0);
+
+ int _multiplier = this.multiplier;
+
+ // used to track attribute starting position for each attribute
+ int attributeStart = -1;
+
+ // now get the sign this will be used by the special case Inifinity
+ // and by the normal cases.
+ boolean isNegative = (number.signum() < 0) ? true : false;
+ if (isNegative)
+ {
+ attributeStart = dest.length();
+
+ // append the negative prefix to the string
+ dest.append(negativePrefix);
+
+ // once got the negative prefix, we can use
+ // the absolute value.
+ number = number.abs();
+
+ _multiplier = negativePatternMultiplier;
+
+ addAttribute(Field.SIGN, attributeStart, dest.length());
+ }
+ else
+ {
+ // not negative, use the positive prefix
+ dest.append(positivePrefix);
+ }
+
+ // these are used ot update the field position
+ int beginIndexInt = dest.length();
+ int endIndexInt = 0;
+ int beginIndexFract = 0;
+ int endIndexFract = 0;
+
+ // compute the multiplier to use with percent and similar
+ number = number.multiply(new BigDecimal(_multiplier));
+
+ // XXX: special case, not sure if it belongs here or if it is
+ // correct at all. There may be other special cases as well
+ // these should be handled in the format string parser.
+ if (this.maximumIntegerDigits == 0 && this.maximumFractionDigits == 0)
+ {
+ number = BigDecimal.ZERO;
+ this.maximumIntegerDigits = 1;
+ this.minimumIntegerDigits = 1;
+ }
+
+ // get the absolute number
+ number = number.abs();
- private static final int MAXIMUM_INTEGER_DIGITS = 309;
+ // the scaling to use while formatting this number
+ int scale = this.maximumFractionDigits;
+
+ // this is the actual number we will use
+ // it is corrected later on to handle exponential
+ // notation, if needed
+ long exponent = 0;
+
+ // are we using exponential notation?
+ if (this.useExponentialNotation)
+ {
+ exponent = getExponent(number);
+ number = number.movePointLeft((int) exponent);
+
+ // FIXME: this makes the test ##.###E0 to pass,
+ // but all all the other tests to fail...
+ // this should be really something like
+ // min + max - what is already shown...
+ //scale = this.minimumIntegerDigits + this.maximumFractionDigits;
+ }
+
+ // round the number to the nearest neighbor
+ number = number.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
- // These names are fixed by the serialization spec.
- private boolean decimalSeparatorAlwaysShown;
- private byte groupingSize;
- private byte minExponentDigits;
- private int exponentRound;
- private int multiplier;
- private String negativePrefix;
- private String negativeSuffix;
- private String positivePrefix;
- private String positiveSuffix;
- private int[] negativePrefixRanges, positivePrefixRanges;
- private HashMap[] negativePrefixAttrs, positivePrefixAttrs;
- private int[] negativeSuffixRanges, positiveSuffixRanges;
- private HashMap[] negativeSuffixAttrs, positiveSuffixAttrs;
- private int serialVersionOnStream = 1;
- private DecimalFormatSymbols symbols;
- private boolean useExponentialNotation;
- private static final long serialVersionUID = 864413376551465018L;
+ // now get the integer and fractional part of the string
+ // that will be processed later
+ String plain = number.toPlainString();
+
+ String intPart = null;
+ String fractPart = null;
+
+ // remove - from the integer part, this is needed as
+ // the Narrowing Primitive Conversions algorithm used may loose
+ // information about the sign
+ int minusIndex = plain.lastIndexOf('-', 0);
+ if (minusIndex > -1) plain = plain.substring(minusIndex + 1);
+
+ // strip the decimal portion
+ int dot = plain.indexOf('.');
+ if (dot > -1)
+ {
+ intPart = plain.substring(0, dot);
+ dot++;
+
+ if (useExponentialNotation)
+ fractPart = plain.substring(dot, dot + scale);
+ else
+ fractPart = plain.substring(dot);
+ }
+ else
+ {
+ intPart = plain;
+ }
+
+ // used in various places later on
+ int intPartLen = intPart.length();
+ endIndexInt = intPartLen;
+
+ // if the number of digits in our intPart is not greater than the
+ // minimum we have to display, we append zero to the destination
+ // buffer before adding the integer portion of the number.
+ int zeroes = minimumIntegerDigits - intPartLen;
+ if (zeroes > 0)
+ {
+ attributeStart = Math.max(dest.length() - 1, 0);
+ appendZero(dest, zeroes, minimumIntegerDigits);
+ }
- private void readObject(ObjectInputStream stream)
- throws IOException, ClassNotFoundException
- {
- stream.defaultReadObject();
- if (serialVersionOnStream < 1)
+ if (this.useExponentialNotation)
+ {
+ // For exponential numbers, the significant in mantissa are
+ // the sum of the minimum integer and maximum fraction
+ // digits, and does not take into account the maximun integer
+ // digits to display.
+
+ if (attributeStart < 0)
+ attributeStart = Math.max(dest.length() - 1, 0);
+ appendDigit(intPart, dest, this.groupingUsed);
+ }
+ else
+ {
+ // non exponential notation
+ intPartLen = intPart.length();
+ int canary = Math.min(intPartLen, this.maximumIntegerDigits);
+
+ // remove from the string the number in excess
+ // use only latest digits
+ intPart = intPart.substring(intPartLen - canary);
+ endIndexInt = intPart.length() + 1;
+
+ // append it
+ if (maximumIntegerDigits > 0 &&
+ !(this.minimumIntegerDigits == 0 &&
+ intPart.compareTo(String.valueOf(symbols.getZeroDigit())) == 0))
+ {
+ if (attributeStart < 0)
+ attributeStart = Math.max(dest.length() - 1, 0);
+ appendDigit(intPart, dest, this.groupingUsed);
+ }
+ }
+
+ // add the INTEGER attribute
+ addAttribute(Field.INTEGER, attributeStart, dest.length());
+
+ if (this.decimalSeparatorAlwaysShown ||
+ ((!isLong || this.useExponentialNotation)
+ && this.showDecimalSeparator &&
+ this.maximumFractionDigits > 0) ||
+ this.minimumFractionDigits > 0)
+ {
+ attributeStart = dest.length();
+
+ if (this.useCurrencySeparator)
+ dest.append(symbols.getMonetaryDecimalSeparator());
+ else
+ dest.append(symbols.getDecimalSeparator());
+
+ // add the INTEGER attribute
+ addAttribute(Field.DECIMAL_SEPARATOR, attributeStart, dest.length());
+ }
+
+ // now handle the fraction portion of the number
+ if ((!isLong || this.useExponentialNotation)
+ && this.maximumFractionDigits > 0
+ || this.minimumFractionDigits > 0)
+ {
+ attributeStart = dest.length();
+ beginIndexFract = attributeStart;
+
+ int digits = this.minimumFractionDigits;
+
+ if (this.useExponentialNotation)
+ {
+ digits = (this.minimumIntegerDigits + this.minimumFractionDigits)
+ - dest.length();
+ if (digits < 0) digits = 0;
+ }
+
+ fractPart = adjustTrailingZeros(fractPart, digits);
+
+ // FIXME: this code must be improved
+ // now check if the factional part is just 0, in this case
+ // we need to remove the '.' unless requested
+ boolean allZeros = true;
+ char fracts[] = fractPart.toCharArray();
+ for (int i = 0; i < fracts.length; i++)
+ {
+ if (fracts[i] != '0')
+ allZeros = false;
+ }
+
+ if (!allZeros || (minimumFractionDigits > 0))
+ {
+ appendDigit(fractPart, dest, false);
+ endIndexFract = dest.length();
+ addAttribute(Field.FRACTION, attributeStart, endIndexFract);
+ }
+ else if (!this.decimalSeparatorAlwaysShown)
+ {
+ dest.deleteCharAt(dest.length() - 1);
+ }
+ else
+ {
+ System.out.println("ayeeeee!");
+ endIndexFract = dest.length();
+ addAttribute(Field.FRACTION, attributeStart, endIndexFract);
+ }
+ }
+
+ // and the exponent
+ if (this.useExponentialNotation)
+ {
+ attributeStart = dest.length();
+
+ dest.append(symbols.getExponential());
+
+ addAttribute(Field.EXPONENT_SYMBOL, attributeStart, dest.length());
+ attributeStart = dest.length();
+
+ if (exponent < 0)
+ {
+ dest.append(symbols.getMinusSign());
+ exponent = -exponent;
+
+ addAttribute(Field.EXPONENT_SIGN, attributeStart, dest.length());
+ }
+
+ attributeStart = dest.length();
+
+ String exponentString = String.valueOf(exponent);
+ int exponentLength = exponentString.length();
+
+ for (int i = 0; i < minExponentDigits - exponentLength; i++)
+ dest.append(symbols.getZeroDigit());
+
+ for (int i = 0; i < exponentLength; ++i)
+ dest.append(exponentString.charAt(i));
+
+ addAttribute(Field.EXPONENT, attributeStart, dest.length());
+ }
+
+ // now include the suffixes...
+ if (isNegative)
{
- useExponentialNotation = false;
- serialVersionOnStream = 1;
+ dest.append(negativeSuffix);
+ }
+ else
+ {
+ dest.append(positiveSuffix);
+ }
+
+ // ...update field position, if needed, and return...
+ if ((fieldPos.getField() == INTEGER_FIELD ||
+ fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
+ {
+ fieldPos.setBeginIndex(beginIndexInt);
+ fieldPos.setEndIndex(endIndexInt);
+ }
+
+ if ((fieldPos.getField() == FRACTION_FIELD ||
+ fieldPos.getFieldAttribute() == NumberFormat.Field.FRACTION))
+ {
+ fieldPos.setBeginIndex(beginIndexFract);
+ fieldPos.setEndIndex(endIndexFract);
}
}
- // The locale-independent pattern symbols happen to be the same as
- // the US symbols.
- private static final DecimalFormatSymbols nonLocalizedSymbols
- = new DecimalFormatSymbols (Locale.US);
+ /**
+ * Append to <code>dest</code>the give number of zeros.
+ * Grouping is added if needed.
+ * The integer totalDigitCount defines the total number of digits
+ * of the number to which we are appending zeroes.
+ */
+ private void appendZero(StringBuffer dest, int zeroes, int totalDigitCount)
+ {
+ char ch = symbols.getZeroDigit();
+ char gSeparator = symbols.getGroupingSeparator();
+
+ int i = 0;
+ int gPos = totalDigitCount;
+ for (i = 0; i < zeroes; i++, gPos--)
+ {
+ if (this.groupingSeparatorInPattern &&
+ (this.groupingUsed && this.groupingSize != 0) &&
+ (gPos % groupingSize == 0 && i > 0))
+ dest.append(gSeparator);
+
+ dest.append(ch);
+ }
+
+ // special case, that requires adding an additional separator
+ if (this.groupingSeparatorInPattern &&
+ (this.groupingUsed && this.groupingSize != 0) &&
+ (gPos % groupingSize == 0))
+ dest.append(gSeparator);
+ }
+
+ /**
+ * Append src to <code>dest</code>.
+ *
+ * Grouping is added if <code>groupingUsed</code> is set
+ * to <code>true</code>.
+ */
+ private void appendDigit(String src, StringBuffer dest,
+ boolean groupingUsed)
+ {
+ int zero = symbols.getZeroDigit() - '0';
+
+ int ch;
+ char gSeparator = symbols.getGroupingSeparator();
+
+ int len = src.length();
+ for (int i = 0, gPos = len; i < len; i++, gPos--)
+ {
+ ch = src.charAt(i);
+ if (groupingUsed && this.groupingSize != 0 &&
+ gPos % groupingSize == 0 && i > 0)
+ dest.append(gSeparator);
+ dest.append((char) (zero + ch));
+ }
+ }
+
/**
- * <p>
- * Substitutes the currency symbol into the given string,
- * based on the value used. Currency symbols can either
- * be a simple series of characters (e.g. '$'), which are
- * simply used as is, or they can be of a more complex
- * form:
- * </p>
- * <p>
- * (lower bound)|(mid value)|(upper bound)
- * </p>
- * <p>
- * where each bound has the syntax '(value)(# or <)(symbol)',
- * to indicate the bounding value and the symbol used.
- * </p>
- * <p>
- * The currency symbol replaces the currency specifier, '\u00a4',
- * an unlocalised character, which thus is used as such in all formats.
- * If this symbol occurs twice, the international currency code is used
- * instead.
- * </p>
- *
- * @param string The string containing the currency specifier, '\u00a4'.
- * @param number the number being formatted.
- * @return a string formatted for the correct currency.
+ * Calculate the exponent to use if eponential notation is used.
+ * The exponent is calculated as a power of ten.
+ * <code>number</code> should be positive, if is zero, or less than zero,
+ * zero is returned.
*/
- private String substituteCurrency(String string, double number)
+ private long getExponent(BigDecimal number)
{
- int index;
- int length;
- char currentChar;
- StringBuffer buf;
+ long exponent = 0;
- index = 0;
- length = string.length();
- buf = new StringBuffer();
+ if (number.signum() > 0)
+ {
+ double _number = number.doubleValue();
+ exponent = (long) Math.floor (Math.log10(_number));
+
+ // get the right value for the exponent
+ exponent = exponent - (exponent % this.exponentRound);
+
+ // if the minimumIntegerDigits is more than zero
+ // we display minimumIntegerDigits of digits.
+ // so, for example, if minimumIntegerDigits == 2
+ // and the actual number is 0.123 it will be
+ // formatted as 12.3E-2
+ // this means that the exponent have to be shifted
+ // to the correct value.
+ if (minimumIntegerDigits > 0)
+ exponent -= minimumIntegerDigits - 1;
+ }
+
+ return exponent;
+ }
+
+ /**
+ * Remove contiguos zeros from the end of the <code>src</code> string,
+ * if src contains more than <code>minimumDigits</code> digits.
+ * if src contains less that <code>minimumDigits</code>,
+ * then append zeros to the string.
+ *
+ * Only the first block of zero digits is removed from the string
+ * and only if they fall in the src.length - minimumDigits
+ * portion of the string.
+ *
+ * @param src The string with the correct number of zeros.
+ */
+ private String adjustTrailingZeros(String src, int minimumDigits)
+ {
+ int len = src.length();
+ String result;
- while (index < length)
+ // remove all trailing zero
+ if (len > minimumDigits)
{
- currentChar = string.charAt(index);
- if (string.charAt(index) == '\u00a4')
- {
- if ((index + 1) < length && string.charAt(index + 1) == '\u00a4')
- {
- buf.append(symbols.getInternationalCurrencySymbol());
- index += 2;
- }
- else
- {
- String symbol;
-
- symbol = symbols.getCurrencySymbol();
- if (symbol.startsWith("="))
- {
- String[] bounds;
- int[] boundValues;
- String[] boundSymbols;
-
- bounds = symbol.substring(1).split("\\|");
- boundValues = new int[3];
- boundSymbols = new String[3];
- for (int a = 0; a < 3; ++a)
- {
- String[] bound;
-
- bound = bounds[a].split("[#<]");
- boundValues[a] = Integer.parseInt(bound[0]);
- boundSymbols[a] = bound[1];
- }
- if (number <= boundValues[0])
- {
- buf.append(boundSymbols[0]);
- }
- else if (number >= boundValues[2])
- {
- buf.append(boundSymbols[2]);
- }
- else
- {
- buf.append(boundSymbols[1]);
- }
- ++index;
- }
- else
- {
- buf.append(symbol);
- ++index;
- }
- }
- }
- else
- {
- buf.append(string.charAt(index));
- ++index;
- }
+ int zeros = 0;
+ for (int i = len - 1; i > minimumDigits; i--)
+ {
+ if (src.charAt(i) == '0')
+ ++zeros;
+ else
+ break;
+ }
+ result = src.substring(0, len - zeros);
}
- return buf.toString();
+ else
+ {
+ char zero = symbols.getZeroDigit();
+ StringBuffer _result = new StringBuffer(src);
+ for (int i = len; i < minimumDigits; i++)
+ {
+ _result.append(zero);
+ }
+ result = _result.toString();
+ }
+
+ return result;
+ }
+
+ /**
+ * Adds an attribute to the attributes list.
+ *
+ * @param field
+ * @param begin
+ * @param end
+ */
+ private void addAttribute(Field field, int begin, int end)
+ {
+ /*
+ * This method and its implementation derives directly from the
+ * ICU4J (http://icu.sourceforge.net/) library, distributed under MIT/X.
+ */
+
+ FieldPosition pos = new FieldPosition(field);
+ pos.setBeginIndex(begin);
+ pos.setEndIndex(end);
+ attributes.add(pos);
}
+ /**
+ * Sets the default values for the various properties in this DecimaFormat.
+ */
+ private void setDefaultValues()
+ {
+ // Maybe we should add these values to the message bundle and take
+ // the most appropriate for them for any locale.
+ // Anyway, these seem to be good values for a default in most languages.
+ // Note that most of these will change based on the format string.
+
+ this.negativePrefix = String.valueOf(symbols.getMinusSign());
+ this.negativeSuffix = "";
+ this.positivePrefix = "";
+ this.positiveSuffix = "";
+
+ this.multiplier = 1;
+ this.negativePatternMultiplier = 1;
+ this.exponentRound = 1;
+
+ this.hasNegativePrefix = false;
+
+ this.minimumIntegerDigits = 1;
+ this.maximumIntegerDigits = DEFAULT_INTEGER_DIGITS;
+ this.minimumFractionDigits = 0;
+ this.maximumFractionDigits = DEFAULT_FRACTION_DIGITS;
+ this.minExponentDigits = 0;
+
+ this.groupingSize = 0;
+
+ this.decimalSeparatorAlwaysShown = false;
+ this.showDecimalSeparator = false;
+ this.useExponentialNotation = false;
+ this.groupingUsed = false;
+ this.groupingSeparatorInPattern = false;
+
+ this.useCurrencySeparator = false;
+
+ this.hasFractionalPattern = false;
+ }
}
diff --git a/java/text/DecimalFormatSymbols.java b/java/text/DecimalFormatSymbols.java
index a8735d361..e4a14a7e3 100644
--- a/java/text/DecimalFormatSymbols.java
+++ b/java/text/DecimalFormatSymbols.java
@@ -69,7 +69,7 @@ public final class DecimalFormatSymbols implements Cloneable, Serializable
{
try
{
- return super.clone ();
+ return super.clone();
}
catch(CloneNotSupportedException e)
{
diff --git a/java/text/NumberFormat.java b/java/text/NumberFormat.java
index 1b96afcd5..1bef97ffe 100644
--- a/java/text/NumberFormat.java
+++ b/java/text/NumberFormat.java
@@ -218,7 +218,7 @@ public abstract class NumberFormat extends Format implements Cloneable
public final String format (long number)
{
StringBuffer sbuf = new StringBuffer(50);
- format (number, sbuf, null);
+ format (number, sbuf, new FieldPosition(0));
return sbuf.toString();
}
@@ -230,9 +230,9 @@ public abstract class NumberFormat extends Format implements Cloneable
{
if (obj instanceof Number)
return format(((Number) obj).doubleValue(), sbuf, pos);
- else
- throw new IllegalArgumentException
- ("Cannot format given Object as a Number");
+
+ throw new
+ IllegalArgumentException("Cannot format given Object as a Number");
}
/**
@@ -354,7 +354,7 @@ public abstract class NumberFormat extends Format implements Cloneable
{
NumberFormat format;
- format = computeInstance (loc, "currencyFormat", "$#,##0.00;($#,##0.00)");
+ format = computeInstance (loc, "currencyFormat", "\u00A4#,##0.00;(\u00A4#,##0.00)");
format.setMaximumFractionDigits(format.getCurrency().getDefaultFractionDigits());
return format;
}
@@ -723,7 +723,9 @@ public abstract class NumberFormat extends Format implements Cloneable
public final String format (double number)
{
StringBuffer sbuf = new StringBuffer(50);
- format (number, sbuf, null);
+ FieldPosition position = new FieldPosition(0);
+
+ format (number, sbuf, position);
return sbuf.toString();
}
diff --git a/java/util/jar/JarEntry.java b/java/util/jar/JarEntry.java
index 722a283bb..515b45fa9 100644
--- a/java/util/jar/JarEntry.java
+++ b/java/util/jar/JarEntry.java
@@ -1,5 +1,5 @@
/* JarEntry.java - Represents an entry in a jar file
- Copyright (C) 2000 Free Software Foundation, Inc.
+ Copyright (C) 2000, 2006 Free Software Foundation, Inc.
This file is part of GNU Classpath.
@@ -39,6 +39,7 @@ package java.util.jar;
import java.io.IOException;
import java.security.cert.Certificate;
+import java.util.Set;
import java.util.zip.ZipEntry;
/**
@@ -60,7 +61,7 @@ public class JarEntry extends ZipEntry
// (Package local) fields
Attributes attr;
- Certificate certs[];
+ JarFile jarfile;
// Constructors
@@ -79,7 +80,7 @@ public class JarEntry extends ZipEntry
{
super(name);
attr = null;
- certs = null;
+ jarfile = null;
}
/**
@@ -93,7 +94,7 @@ public class JarEntry extends ZipEntry
{
super(entry);
attr = null;
- certs = null;
+ jarfile = null;
}
/**
@@ -112,7 +113,7 @@ public class JarEntry extends ZipEntry
catch (IOException _)
{
}
- certs = entry.getCertificates();
+ jarfile = entry.jarfile;
}
// Methods
@@ -153,13 +154,19 @@ public class JarEntry extends ZipEntry
*/
public Certificate[] getCertificates()
{
- if (certs != null)
+ if (jarfile != null)
{
- return (Certificate[])certs.clone();
- }
- else
- {
- return null;
+ synchronized (jarfile)
+ {
+ if (jarfile.entryCerts != null)
+ {
+ Set certs = (Set) jarfile.entryCerts.get(getName());
+ if (certs != null
+ && jarfile.verified.get(getName()) == Boolean.TRUE)
+ return (Certificate[]) certs.toArray(new Certificate[certs.size()]);
+ }
+ }
}
+ return null;
}
}
diff --git a/java/util/jar/JarFile.java b/java/util/jar/JarFile.java
index 003d9cb5e..680773659 100644
--- a/java/util/jar/JarFile.java
+++ b/java/util/jar/JarFile.java
@@ -68,6 +68,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
@@ -149,6 +151,12 @@ public class JarFile extends ZipFile
*/
HashMap entryCerts;
+ /**
+ * A {@link Map} of message digest algorithm names to their implementation.
+ * Used to reduce object (algorithm implementation) instantiation.
+ */
+ private HashMap digestAlgorithms = new HashMap();
+
static boolean DEBUG = false;
static void debug(Object msg)
{
@@ -374,19 +382,8 @@ public class JarFile extends ZipFile
}
jarfile.signaturesRead = true; // fudge it.
}
-
- // Include the certificates only if we have asserted that the
- // signatures are valid. This means the certificates will not be
- // available if the entry hasn't been read yet.
- if (jarfile.entryCerts != null
- && jarfile.verified.get(zip.getName()) == Boolean.TRUE)
- {
- Set certs = (Set) jarfile.entryCerts.get(jar.getName());
- if (certs != null)
- jar.certs = (Certificate[])
- certs.toArray(new Certificate[certs.size()]);
- }
}
+ jar.jarfile = jarfile;
return jar;
}
}
@@ -431,18 +428,7 @@ public class JarFile extends ZipFile
}
signaturesRead = true;
}
- // See the comments in the JarEnumeration for why we do this
- // check.
- if (DEBUG)
- debug("entryCerts=" + entryCerts + " verified " + name
- + " ? " + verified.get(name));
- if (entryCerts != null && verified.get(name) == Boolean.TRUE)
- {
- Set certs = (Set) entryCerts.get(name);
- if (certs != null)
- jarEntry.certs = (Certificate[])
- certs.toArray(new Certificate[certs.size()]);
- }
+ jarEntry.jarfile = this;
return jarEntry;
}
return null;
@@ -599,6 +585,31 @@ public class JarFile extends ZipFile
validCerts.clear();
}
+ // Read the manifest into a HashMap (String fileName, String entry)
+ // The fileName might be split into multiple lines in the manifest.
+ // Such additional lines will start with a space.
+ InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
+ ByteArrayOutputStream baStream = new ByteArrayOutputStream();
+ byte[] ba = new byte[1024];
+ while (true)
+ {
+ int len = in.read(ba);
+ if (len < 0)
+ break;
+ baStream.write(ba, 0, len);
+ }
+ in.close();
+
+ HashMap hmManifestEntries = new HashMap();
+ Pattern p = Pattern.compile("Name: (.+?\r?\n(?: .+?\r?\n)*)"
+ + ".+?-Digest: .+?\r?\n\r?\n");
+ Matcher m = p.matcher(baStream.toString());
+ while (m.find())
+ {
+ String fileName = m.group(1).replaceAll("\r?\n ?", "");
+ hmManifestEntries.put(fileName, m.group());
+ }
+
// Phase 3: verify the signature file signatures against the manifest,
// mapping the entry name to the target certificates.
this.entryCerts = new HashMap();
@@ -614,7 +625,7 @@ public class JarFile extends ZipFile
Map.Entry e2 = (Map.Entry) it2.next();
String entryname = String.valueOf(e2.getKey());
Attributes attr = (Attributes) e2.getValue();
- if (verifyHashes(entryname, attr))
+ if (verifyHashes(entryname, attr, hmManifestEntries))
{
if (DEBUG)
debug("entry " + entryname + " has certificates " + certificates);
@@ -721,39 +732,29 @@ public class JarFile extends ZipFile
}
/**
- * Verifies that the digest(s) in a signature file were, in fact, made
- * over the manifest entry for ENTRY.
- *
+ * Verifies that the digest(s) in a signature file were, in fact, made over
+ * the manifest entry for ENTRY.
+ *
* @param entry The entry name.
* @param attr The attributes from the signature file to verify.
+ * @param hmManifestEntries Mappings of Jar file entry names to their manifest
+ * entry text; i.e. the base-64 encoding of their
*/
- private boolean verifyHashes(String entry, Attributes attr)
+ private boolean verifyHashes(String entry, Attributes attr,
+ HashMap hmManifestEntries)
{
int verified = 0;
- // The bytes for ENTRY's manifest entry, which are signed in the
- // signature file.
- byte[] entryBytes = null;
- try
- {
- ZipEntry e = super.getEntry(entry);
- if (e == null)
- {
- if (DEBUG)
- debug("verifyHashes: no entry '" + entry + "'");
- return false;
- }
- entryBytes = readManifestEntry(e);
- }
- catch (IOException ioe)
+ String stringEntry = (String) hmManifestEntries.get(entry);
+ if (stringEntry == null)
{
if (DEBUG)
- {
- debug(ioe);
- ioe.printStackTrace();
- }
+ debug("could not find " + entry + " in manifest");
return false;
}
+ // The bytes for ENTRY's manifest entry, which are signed in the
+ // signature file.
+ byte[] entryBytes = stringEntry.getBytes();
for (Iterator it = attr.entrySet().iterator(); it.hasNext(); )
{
@@ -765,9 +766,14 @@ public class JarFile extends ZipFile
try
{
byte[] hash = Base64InputStream.decode((String) e.getValue());
- MessageDigest md = MessageDigest.getInstance(alg, provider);
- md.update(entryBytes);
- byte[] hash2 = md.digest();
+ MessageDigest md = (MessageDigest) digestAlgorithms.get(alg);
+ if (md == null)
+ {
+ md = MessageDigest.getInstance(alg, provider);
+ digestAlgorithms.put(alg, md);
+ }
+ md.reset();
+ byte[] hash2 = md.digest(entryBytes);
if (DEBUG)
debug("verifying SF entry " + entry + " alg: " + md.getAlgorithm()
+ " expect=" + new java.math.BigInteger(hash).toString(16)
@@ -801,100 +807,6 @@ public class JarFile extends ZipFile
}
/**
- * Read the raw bytes that comprise a manifest entry. We can't use the
- * Manifest object itself, because that loses information (such as line
- * endings, and order of entries).
- */
- private byte[] readManifestEntry(ZipEntry entry) throws IOException
- {
- InputStream in = super.getInputStream(super.getEntry(MANIFEST_NAME));
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- byte[] target = ("Name: " + entry.getName()).getBytes();
- int t = 0, c, prev = -1, state = 0, l = -1;
-
- while ((c = in.read()) != -1)
- {
-// if (DEBUG)
-// debug("read "
-// + (c == '\n' ? "\\n" : (c == '\r' ? "\\r" : String.valueOf((char) c)))
-// + " state=" + state + " prev="
-// + (prev == '\n' ? "\\n" : (prev == '\r' ? "\\r" : String.valueOf((char) prev)))
-// + " t=" + t + (t < target.length ? (" target[t]=" + (char) target[t]) : "")
-// + " l=" + l);
- switch (state)
- {
-
- // Step 1: read until we find the "target" bytes: the start
- // of the entry we need to read.
- case 0:
- if (((byte) c) != target[t])
- t = 0;
- else
- {
- t++;
- if (t == target.length)
- {
- out.write(target);
- state = 1;
- }
- }
- break;
-
- // Step 2: assert that there is a newline character after
- // the "target" bytes.
- case 1:
- if (c != '\n' && c != '\r')
- {
- out.reset();
- t = 0;
- state = 0;
- }
- else
- {
- out.write(c);
- state = 2;
- }
- break;
-
- // Step 3: read this whole entry, until we reach an empty
- // line.
- case 2:
- if (c == '\n')
- {
- out.write(c);
- // NL always terminates a line.
- if (l == 0 || (l == 1 && prev == '\r'))
- return out.toByteArray();
- l = 0;
- }
- else
- {
- // Here we see a blank line terminated by a CR,
- // followed by the next entry. Technically, `c' should
- // always be 'N' at this point.
- if (l == 1 && prev == '\r')
- return out.toByteArray();
- out.write(c);
- l++;
- }
- prev = c;
- break;
-
- default:
- throw new RuntimeException("this statement should be unreachable");
- }
- }
-
- // The last entry, with a single CR terminating the line.
- if (state == 2 && prev == '\r' && l == 0)
- return out.toByteArray();
-
- // We should not reach this point, we didn't find the entry (or, possibly,
- // it is the last entry and is malformed).
- throw new IOException("could not find " + entry + " in manifest");
- }
-
- /**
* A utility class that verifies jar entries as they are read.
*/
private static class EntryInputStream extends FilterInputStream
diff --git a/javax/management/MBeanPermission.java b/javax/management/MBeanPermission.java
new file mode 100644
index 000000000..2c8dfbd62
--- /dev/null
+++ b/javax/management/MBeanPermission.java
@@ -0,0 +1,562 @@
+/* MBeanPermission.java -- Permissions controlling server access.
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.management;
+
+import java.security.Permission;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * <p>
+ * Represents the permissions required to perform
+ * operations using the {@link MBeanServer}. As with
+ * all {@link java.security.Permission} objects, an
+ * instance of this class either represents a permission
+ * already held or one that is required to access a
+ * particular service. In the case of {@link MBeanPermission}s,
+ * implication checks are made using an instance of this class
+ * when a user requests an operation from the server, and a
+ * {@link SecurityManager} is in place.
+ * </p>
+ * <p>
+ * An {@link MBeanPermission} consists of four elements,
+ * which all have to match for the permission to be implied.
+ * These are as follows:
+ * </p>
+ * <ol>
+ * <li><strong>The action</strong>. For a required permission,
+ * this is a single value. For a permission held by the user,
+ * this is a list of comma-separated actions (with spaces allowed),
+ * or <code>*</code> (representing all actions). {@link #getActions()}
+ * returns this value.</li>
+ * <li><strong>The class name</strong>. For a required permission,
+ * this is the class name of the bean being accessed, if any. If
+ * a bean isn't involved in this action, the value is <code>null</code>.
+ * For a permission held by the user, it has one of three values:
+ * <ol>
+ * <li>The empty string, implying any class.</li>
+ * <li><code>*</code>, also implying any class.</li>
+ * <li>A class name pattern, which may specify a single class
+ * (e.g. <code>java.lang.Object</code>) or a series of classes
+ * using the wildcard character <code>*</code> (e.g.
+ * <code>javax.swing.*</code>.)</li>
+ * </ol></li>
+ * <li><strong>The member</strong>. For a required permission,
+ * this is the member of the bean being accessed (an attribute
+ * or operation), if any. If a member of the bean isn't involved
+ * in this action, the value is <code>null</code>.
+ * For a permission held by the user, it has one of three values:
+ * <ol>
+ * <li>The empty string, implying any member.</li>
+ * <li><code>*</code>, also implying any member.</li>
+ * <li>The name of a member.</li>
+ * </ol></li>
+ * <li>The object name</strong>. For a required permission,
+ * this is the {@link ObjectName} of the bean being accessed, if
+ * any. If a bean isn't involved in this action, the value is
+ * <code>null</code>. The name may not be a pattern.
+ * For a permission held by the user, it may be the empty
+ * string (allowing everything) or an {@link ObjectName}
+ * pattern.
+ * </li></ol>
+ * {@link #getName()} returns the latter three of these as a
+ * single string:
+ * </p>
+ * <p><code>className#member[objectName]</code></p>
+ * <p>
+ * where <code>""</code> is disallowed, as, although any of
+ * the elements may be omitted, not all of them should be
+ * left out simultaneously. <code>"-"</code> is used to
+ * represent <code>null</code>. When this occurs in a
+ * required permission, anything may match it. When this
+ * forms part of a permission held by the user, it only
+ * matches another <code>null</code> value.
+ * </p>
+ * <p>The list of valid actions is as follows:</p>
+ * <ul>
+ * <li>addNotificationListener</li>
+ * <li>getAttribute</li>
+ * <li>getClassLoader</li>
+ * <li>getClassLoaderFor</li>
+ * <li>getClassLoaderRepository</li>
+ * <li>getDomains</li>
+ * <li>getMBeanInfo</li>
+ * <li>getObjectInstance</li>
+ * <li>instantiate</li>
+ * <li>invoke</li>
+ * <li>isInstanceOf</li>
+ * <li>queryMBeans</li>
+ * <li>queryNames</li>
+ * <li>registerMBean</li>
+ * <li>removeNotificationListener</li>
+ * <li>setAttribute</li>
+ * <li>unregisterMBean</li>
+ * </ul>
+ *
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ * @since 1.5
+ */
+public class MBeanPermission
+ extends Permission
+{
+
+ /**
+ * Compatible with JDK 1.5
+ */
+ private static final long serialVersionUID = -2416928705275160661L;
+
+ /**
+ * The list of actions associated with this permission.
+ */
+ private String actions;
+
+ /**
+ * The list of actions as an ordered set.
+ */
+ private transient Set actionSet;
+
+ /**
+ * The set of valid actions.
+ */
+ private static final Set validSet;
+
+ /**
+ * Initialise the set of valid actions.
+ */
+ static
+ {
+ validSet = new HashSet();
+ validSet.add("addNotificationListener");
+ validSet.add("getAttribute");
+ validSet.add("getClassLoader");
+ validSet.add("getClassLoaderFor");
+ validSet.add("getClassLoaderRepository");
+ validSet.add("getDomains");
+ validSet.add("getMBeanInfo");
+ validSet.add("getObjectInstance");
+ validSet.add("instantiate");
+ validSet.add("invoke");
+ validSet.add("isInstanceOf");
+ validSet.add("queryMBeans");
+ validSet.add("queryNames");
+ validSet.add("registerMBean");
+ validSet.add("removeNotificationListener");
+ validSet.add("setAttribute");
+ validSet.add("unregisterMBean");
+ }
+
+ /**
+ * Constructs a new {@link MBeanPermission} with the specified name
+ * and actions. The name is of the form <code>className#member[objectName]</code>,
+ * where each element is optional, but a completely empty or <code>null</code>
+ * name is disallowed. Actions are specified as a comma-separated list
+ * and may also not be empty or <code>null</code>.
+ *
+ * @param name the name of the permission.
+ * @param actions the actions associated with this permission.
+ * @throws IllegalArgumentException if the name or actions are invalid.
+ */
+ public MBeanPermission(String name, String actions)
+ {
+ super(name);
+ if (name == null || name.length() == 0)
+ throw new IllegalArgumentException("The supplied name was null or empty.");
+ if (actions == null || actions.length() == 0)
+ throw new IllegalArgumentException("The supplied action list was null or empty.");
+ this.actions = actions;
+ updateActionSet();
+ }
+
+ /**
+ * Constructs a new {@link MBeanPermission} with the specified class name,
+ * member, object name and actions. The name of the permission is created
+ * using the form <code>className#member[objectName]</code>,
+ * where each element is optional, but an empty or <code>null</code>
+ * name is disallowed. Actions are specified as a comma-separated list
+ * and may also not be empty or <code>null</code>.
+ *
+ * @param className the name of the class to which this permission applies,
+ * or either <code>null</code> or <code>"-"</code> for a
+ * value which may be implied by any class name, but not
+ * imply any class name itself.
+ * @param member the member of the class to which this permission applies,
+ * or either <code>null</code> or <code>"-"</code> for a
+ * value which may be implied by any member, but not
+ * imply any member itself.
+ * @param objectName the {@link ObjectName} to which this permission applies,
+ * or <code>null</code> for a value which may be implied by
+ * any object name, but not imply any object name itself.
+ * @param actions the actions associated with this permission.
+ */
+ public MBeanPermission(String className, String member,
+ ObjectName name, String actions)
+ {
+ this((className == null ? "-" : className) + "#"
+ + (member == null ? "-" : member) + "["
+ + (name == null ? "-" : name.toString()) + "]", actions);
+ }
+
+ /**
+ * Returns true if the given object is also an {@link MBeanPermission}
+ * with the same name and actions.
+ *
+ * @param obj the object to test.
+ * @return true if the object is an {@link MBeanPermission} with
+ * the same name and actions.
+ */
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof MBeanPermission)
+ {
+ MBeanPermission p = (MBeanPermission) obj;
+ return (p.getName().equals(getName()) &&
+ p.getActions().equals(actions));
+ }
+ return false;
+ }
+
+ /**
+ * Returns the list of actions in alphabetical order.
+ *
+ * @return the list of actions.
+ */
+ public String getActions()
+ {
+ Iterator it = actionSet.iterator();
+ StringBuilder builder = new StringBuilder();
+ while (it.hasNext())
+ {
+ builder.append(it.next());
+ if (it.hasNext())
+ builder.append(",");
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Returns the hashcode of the permission as the sum
+ * of the hashcodes of the name and actions.
+ *
+ * @return the hashcode of the permission.
+ */
+ public int hashCode()
+ {
+ return getName().hashCode() + actions.hashCode();
+ }
+
+ /**
+ * <p>
+ * Returns true if this permission implies the supplied permission.
+ * This happens if the following holds:
+ * </p>
+ * <ul>
+ * <li>The supplied permission is an {@link MBeanPermission}</li>
+ * <li>The supplied permission has either a <code>null</code> classname
+ * or its classname matches the classname of this permission. A
+ * classname of <code>"*"</code> for this permission always matches
+ * the classname of the supplied permission. Generally, <code>'*'</code>
+ * acts as a wildcard, so <code>".*"</code> matches <code>'.'</code>
+ * followed by anything.</li>
+ * <li>The supplied permission has either a <code>null</code> member
+ * or its member matches the member of this permission. A member of
+ * <code>"*"</code> for this permission always matches the member
+ * of the supplied permission.</li>
+ * <li>The supplied permission has either a <code>null</code> object name
+ * or its object name matches the object name of this permission. If the
+ * object name of this permission is a pattern, {@link ObjectName#apply(ObjectName)}
+ * may be used as well.</li>
+ * <li>The supplied permission's actions are a subset of the actions
+ * of this permission. If the <code>queryMBeans</code> action is presented,
+ * the <code>queryNames</code> action is implied.</li>
+ * </ul>
+ *
+ * @param p the permission to check that this permission implies.
+ * @return true if this permission implies <code>p</code>.
+ */
+ public boolean implies(Permission p)
+ {
+ if (p instanceof MBeanPermission)
+ {
+ MBeanPermission mp = (MBeanPermission) p;
+ NameHolder pName = new NameHolder(mp.getName());
+ NameHolder name = new NameHolder(getName());
+ if (!(name.equals(pName)))
+ return false;
+ Iterator i = mp.getActionSet().iterator();
+ while (i.hasNext())
+ {
+ String nextAction = (String) i.next();
+ boolean found = actions.contains(nextAction);
+ if (!found)
+ if (nextAction.equals("queryNames"))
+ found = actions.contains("queryMBeans");
+ if (!found)
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Small helper class to handle deconstruction of the name.
+ *
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ */
+ private class NameHolder
+ {
+
+ /**
+ * The class name.
+ */
+ private String className;
+
+ /**
+ * The member.
+ */
+ private String member;
+
+ /**
+ * The object name.
+ */
+ private ObjectName objectName;
+
+ /**
+ * Constructs a broken-down name from a given name.
+ *
+ * @param name the name to break down.
+ */
+ public NameHolder(String name)
+ {
+ String objectName = null;
+ int memberIndex = name.indexOf("#");
+ int onIndex = name.indexOf("[");
+ if (onIndex == -1)
+ {
+ if (memberIndex == -1)
+ className = name;
+ else
+ {
+ className = name.substring(0, memberIndex);
+ member = name.substring(memberIndex + 1);
+ }
+ }
+ else
+ {
+ if (memberIndex == -1)
+ {
+ className = name.substring(0, onIndex);
+ objectName = name.substring(onIndex + 1,
+ name.length() - 1);
+ }
+ else
+ {
+ className = name.substring(0, memberIndex);
+ member = name.substring(memberIndex + 1, onIndex);
+ objectName = name.substring(onIndex + 1,
+ name.length() - 1);
+ }
+ }
+ if (className.equals("-"))
+ className = null;
+ if (member.equals("-"))
+ member = null;
+ if (objectName == null || objectName.equals("-"))
+ this.objectName = null;
+ else
+ try
+ {
+ this.objectName = new ObjectName(objectName);
+ }
+ catch (MalformedObjectNameException e)
+ {
+ throw (Error)
+ (new InternalError("Invalid object name.").initCause(e));
+ }
+ }
+
+ /**
+ * <p>
+ * Returns true if the supplied object is also a
+ * {@link NameHolder} and the following holds:
+ * </p>
+ * <ul>
+ * <li>The supplied classname is <code>null</code> or the two match. A
+ * classname of <code>"*"</code> for this holder always matches
+ * the classname of the supplied holder. Generally, <code>'*'</code>
+ * acts as a wildcard, so <code>".*"</code> matches <code>'.'</code>
+ * followed by anything.</li>
+ * <li>The supplied name holder has either a <code>null</code> member
+ * or its member matches the member of this name holder. A member of
+ * <code>"*"</code> for this name holder always matches the member
+ * of the supplied name holder.</li>
+ * <li>The supplied name holder has either a <code>null</code> object name
+ * or its object name matches the object name of this name holder. If the
+ * object name of this name holder is a pattern,
+ * {@link ObjectName#apply(ObjectName)} may be used as well.</li>
+ * </ul>
+ *
+ * @param obj the object to compare with this.
+ * @return true if the above holds.
+ */
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof NameHolder)
+ {
+ NameHolder nh = (NameHolder) obj;
+ boolean cn = false;
+ String ocn = nh.getClassName();
+ if (ocn == null || className.equals("*"))
+ cn = true;
+ else
+ {
+ int wcIndex = className.indexOf("*");
+ if (wcIndex != -1)
+ cn = ocn.startsWith(className.substring(0, wcIndex));
+ else
+ cn = ocn.equals(className);
+ }
+ boolean m = false;
+ String om = nh.getMember();
+ if (om == null || member.equals("*"))
+ m = true;
+ else
+ m = om.equals(member);
+ boolean on = false;
+ ObjectName oon = nh.getObjectName();
+ if (oon == null)
+ on = true;
+ else if (objectName.isPattern())
+ on = objectName.apply(oon);
+ else
+ on = oon.equals(objectName);
+ return (cn && m && on);
+ }
+ return false;
+ }
+
+ /**
+ * Returns the class name.
+ */
+ public String getClassName()
+ {
+ return className;
+ }
+
+ /**
+ * Returns the member.
+ */
+ public String getMember()
+ {
+ return member;
+ }
+
+ /**
+ * Returns the object name.
+ */
+ public ObjectName getObjectName()
+ {
+ return objectName;
+ }
+ }
+
+ /**
+ * Returns the set of actions.
+ *
+ * @return the actions as an ordered set.
+ */
+ Set getActionSet()
+ {
+ return actionSet;
+ }
+
+ /**
+ * Updates the action set from the current value of
+ * the actions string.
+ */
+ private void updateActionSet()
+ {
+ String[] actionsArray = actions.split(",");
+ actionSet = new TreeSet();
+ for (int a = 0; a < actionsArray.length; ++a)
+ actionSet.add(actionsArray[a].trim());
+ }
+
+ /**
+ * Reads the object from a stream and ensures the incoming
+ * data is valid.
+ *
+ * @param in the input stream.
+ * @throws IOException if an I/O error occurs.
+ * @throws ClassNotFoundException if a class used by the object
+ * can not be found.
+ */
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException
+ {
+ in.defaultReadObject();
+ updateActionSet();
+ checkActions();
+ }
+
+ /**
+ * Checks that the actions used in this permission
+ * are from the valid set.
+ *
+ * @throws IllegalArgumentException if the name or actions are invalid.
+ */
+ private void checkActions()
+ {
+ Iterator it = actionSet.iterator();
+ while (it.hasNext())
+ {
+ String action = (String) it.next();
+ if (!(validSet.contains(action)))
+ throw new IllegalArgumentException("Invalid action "
+ + action + " found.");
+ }
+ }
+
+}
+
diff --git a/javax/management/MBeanRegistration.java b/javax/management/MBeanRegistration.java
new file mode 100644
index 000000000..5a181ca49
--- /dev/null
+++ b/javax/management/MBeanRegistration.java
@@ -0,0 +1,95 @@
+/* MBeanRegistration.java -- Interface for beans to hook into registration.
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.management;
+
+/**
+ * Beans may implement this interface in order to perform
+ * operations immediately prior to or after their registration
+ * or deregistration.
+ *
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ * @since 1.5
+ */
+public interface MBeanRegistration
+{
+
+ /**
+ * This method is called following deregistration of the bean
+ * by the server.
+ */
+ void postDeregister();
+
+ /**
+ * This method is called following both successful and unsuccessful
+ * attempts to register the bean. The supplied boolean value indicates
+ * the result of the attempt relative to this call.
+ *
+ * @param successful true if the registration was successful.
+ */
+ void postRegister(Boolean successful);
+
+ /**
+ * This method is called prior to de-registration, and may throw
+ * an exception.
+ *
+ * @throws Exception if something goes wrong during the bean's pre-deregistration
+ * operation. The server will re-throw this exception
+ * as an {@link MBeanRegistrationException}.
+ */
+ void preDeregister()
+ throws Exception;
+
+ /**
+ * This method is called prior to registration, with a reference to the
+ * server and {@link ObjectName} supplied to the server for registration.
+ * This method may be used to replace this name by one chosen by the bean.
+ * Such behaviour is expected if the supplied name is <code>null</code>,
+ * but may occur in all cases. The method may throw an exception, which
+ * will cause registration to be aborted.
+ *
+ * @param server the server with which the bean is being registered.
+ * @param name the name the server was supplied with for registration,
+ * which may be <code>null</code>.
+ * @throws Exception if something goes wrong during the bean's pre-registration
+ * operation. The server will re-throw this exception
+ * as an {@link MBeanRegistrationException}.
+ */
+ ObjectName preRegister(MBeanServer server, ObjectName name)
+ throws Exception;
+
+}
diff --git a/javax/management/MBeanTrustPermission.java b/javax/management/MBeanTrustPermission.java
new file mode 100644
index 000000000..57d0e6889
--- /dev/null
+++ b/javax/management/MBeanTrustPermission.java
@@ -0,0 +1,105 @@
+/* MBeanTrustPermission.java -- Represents a trusted bean source.
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+package javax.management;
+
+import java.security.BasicPermission;
+
+/**
+ * Represents the permission held by a trusted source of
+ * management beans. For a bean to be added to a management
+ * server, the source of that bean must hold this permission.
+ * It has a target, but no actions. Valid values for the target
+ * are <code>"register"</code> and <code>"*"</code>, the latter
+ * representing both the existing <code>"register"</code> target
+ * and any future targets.
+ *
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ * @since 1.5
+ */
+public class MBeanTrustPermission
+ extends BasicPermission
+{
+
+ /**
+ * Compatible with JDK 1.5
+ */
+ private static final long serialVersionUID = -2952178077029018140L;
+
+ /**
+ * Constructs a {@link MBeanTrustPermission} with the given target.
+ * The target must be either <code>"register"</code> or <code>"*"</code>.
+ * The actions of the permission default to <code>null</code>,
+ * so this is equivalent to calling
+ * <code>MBeanTrustPermission(target, null)</code>.
+ *
+ * @param target the target of this permission.
+ * @throws NullPointerException if <code>target</code> is null.
+ * @throws IllegalArgumentException if the target is other than
+ * <code>"register"</code> or <code>"*"</code>.
+ * @see #MBeanTrustPermission(String, String)
+ */
+ public MBeanTrustPermission(String target)
+ {
+ this(target, null);
+ }
+
+ /**
+ * Constructs a {@link MBeanTrustPermission} with the given target
+ * and actions. The target must be either <code>"register"</code>
+ * or <code>"*"</code>. The actions must be either <code>null</code>
+ * or the empty string, <code>""</code>.
+ *
+ * @param target the target of this permission.
+ * @param actions the actions for this permission.
+ * @throws NullPointerException if <code>target</code> is null.
+ * @throws IllegalArgumentException if the target is other than
+ * <code>"register"</code> or <code>"*"</code>
+ * or actions is other than
+ * <code>null</code> or <code>""</code>.
+ */
+ public MBeanTrustPermission(String target, String actions)
+ {
+ super(target, actions);
+ if ((!(target.equals("register"))) &&
+ (!(target.equals("*"))))
+ throw new IllegalArgumentException("The target must be 'register' or '*'");
+ if (actions != null && !(actions.length() == 0))
+ throw new IllegalArgumentException("The actions must be null or ''");
+ }
+
+}
diff --git a/javax/swing/ButtonGroup.java b/javax/swing/ButtonGroup.java
index 70ec4ad8a..6a474f98d 100644
--- a/javax/swing/ButtonGroup.java
+++ b/javax/swing/ButtonGroup.java
@@ -183,6 +183,10 @@ public class ButtonGroup implements Serializable
if (old != null)
old.setSelected(false);
+
+ if (m != null)
+ sel.setSelected(true);
+
AbstractButton button = findButton(old);
if (button != null)
button.repaint();
diff --git a/javax/swing/JComponent.java b/javax/swing/JComponent.java
index 3b8f1f68c..5ec507922 100644
--- a/javax/swing/JComponent.java
+++ b/javax/swing/JComponent.java
@@ -847,7 +847,12 @@ public abstract class JComponent extends Container implements Serializable
t.put(key, value);
else
t.remove(key);
- firePropertyChange(key.toString(), old, value);
+
+ // When both old and new value are null, no event is fired. This is
+ // different from what firePropertyChange() normally does, so we add this
+ // check here.
+ if (old != null || value != null)
+ firePropertyChange(key.toString(), old, value);
}
/**
diff --git a/javax/swing/JEditorPane.java b/javax/swing/JEditorPane.java
index a503bb6e8..4007169bd 100644
--- a/javax/swing/JEditorPane.java
+++ b/javax/swing/JEditorPane.java
@@ -40,6 +40,8 @@ package javax.swing;
import java.awt.Container;
import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -58,6 +60,7 @@ import javax.accessibility.AccessibleText;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.plaf.TextUI;
+import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
@@ -507,6 +510,121 @@ public class JEditorPane extends JTextComponent
}
}
+ /**
+ * A special stream that can be cancelled.
+ */
+ private class PageStream
+ extends FilterInputStream
+ {
+ /**
+ * True when the stream has been cancelled, false otherwise.
+ */
+ private boolean cancelled;
+
+ protected PageStream(InputStream in)
+ {
+ super(in);
+ cancelled = false;
+ }
+
+ private void checkCancelled()
+ throws IOException
+ {
+ if (cancelled)
+ throw new IOException("Stream has been cancelled");
+ }
+
+ void cancel()
+ {
+ cancelled = true;
+ }
+
+ public int read()
+ throws IOException
+ {
+ checkCancelled();
+ return super.read();
+ }
+
+ public int read(byte[] b, int off, int len)
+ throws IOException
+ {
+ checkCancelled();
+ return super.read(b, off, len);
+ }
+
+ public long skip(long n)
+ throws IOException
+ {
+ checkCancelled();
+ return super.skip(n);
+ }
+
+ public int available()
+ throws IOException
+ {
+ checkCancelled();
+ return super.available();
+ }
+
+ public void reset()
+ throws IOException
+ {
+ checkCancelled();
+ super.reset();
+ }
+ }
+
+ /**
+ * The thread that loads documents asynchronously.
+ */
+ private class PageLoader
+ implements Runnable
+ {
+ private Document doc;
+ private InputStream in;
+ private URL old;
+ private URL page;
+ PageLoader(Document doc, InputStream in, int prio, URL old, URL page)
+ {
+ this.doc = doc;
+ this.in = in;
+ this.old = old;
+ this.page = page;
+ }
+
+ public void run()
+ {
+ try
+ {
+ read(in, doc);
+ synchronized (JEditorPane.this)
+ {
+ loading = null;
+ }
+ }
+ catch (IOException ex)
+ {
+ UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
+ }
+ finally
+ {
+ if (SwingUtilities.isEventDispatchThread())
+ firePropertyChange("page", old, page);
+ else
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ firePropertyChange("page", old, page);
+ }
+ });
+ }
+ }
+ }
+ }
+
private static final long serialVersionUID = 3140472492599046285L;
private EditorKit editorKit;
@@ -519,6 +637,11 @@ public class JEditorPane extends JTextComponent
// A mapping between content types and used EditorKits
HashMap editorMap;
+ /**
+ * The currently loading stream, if any.
+ */
+ private PageStream loading;
+
public JEditorPane()
{
init();
@@ -936,16 +1059,60 @@ public class JEditorPane extends JTextComponent
if (page == null)
throw new IOException("invalid url");
- URL old = getPage();;
- InputStream in = getStream(page);
- if (editorKit != null)
+ URL old = getPage();
+ // Reset scrollbar when URL actually changes.
+ if (! page.equals(old) && page.getRef() == null)
+ scrollRectToVisible(new Rectangle(0, 0, 1, 1));
+
+ // Only reload if the URL doesn't point to the same file.
+ // This is not the same as equals because there might be different
+ // URLs on the same file with different anchors.
+ if (old == null || ! old.sameFile(page))
{
- Document doc = editorKit.createDefaultDocument();
- doc.putProperty(Document.StreamDescriptionProperty, page);
- read(in, doc);
- setDocument(doc);
+ InputStream in = getStream(page);
+ if (editorKit != null)
+ {
+ Document doc = editorKit.createDefaultDocument();
+ doc.putProperty(Document.StreamDescriptionProperty, page);
+
+ // Cancel loading stream, if there is any.
+ synchronized (this)
+ {
+ if (loading != null)
+ {
+ loading.cancel();
+ loading = null;
+ }
+ }
+ int prio = -1;
+ if (doc instanceof AbstractDocument)
+ {
+ AbstractDocument aDoc = (AbstractDocument) doc;
+ prio = aDoc.getAsynchronousLoadPriority();
+ }
+ if (prio >= 0)
+ {
+ // Load asynchronously.
+ setDocument(doc);
+ synchronized (this)
+ {
+ loading = new PageStream(in);
+ }
+ PageLoader loader = new PageLoader(doc, loading, prio, old,
+ page);
+ Thread loadThread = new Thread(loader);
+ loadThread.setPriority(prio);
+ loadThread.start();
+ }
+ else
+ {
+ // Load synchronously.
+ PageLoader loader = new PageLoader(doc, in, prio, old, page);
+ loader.run();
+ setDocument(doc);
+ }
+ }
}
- firePropertyChange("page", old, page);
}
/**
diff --git a/javax/swing/JRootPane.java b/javax/swing/JRootPane.java
index a2cd9c7a0..10fdf10c0 100644
--- a/javax/swing/JRootPane.java
+++ b/javax/swing/JRootPane.java
@@ -505,15 +505,21 @@ public class JRootPane extends JComponent implements Accessible
}
/**
- * DOCUMENT ME!
+ * Set the layered pane for the root pane.
*
- * @param f DOCUMENT ME!
+ * @param f The JLayeredPane to be used.
+ *
+ * @throws IllegalComponentStateException if JLayeredPane
+ * parameter is null.
*/
public void setLayeredPane(JLayeredPane f)
{
+ if (f == null)
+ throw new IllegalComponentStateException();
+
if (layeredPane != null)
remove(layeredPane);
-
+
layeredPane = f;
add(f, -1);
}
diff --git a/javax/swing/JSlider.java b/javax/swing/JSlider.java
index 91eec4751..948a9629b 100644
--- a/javax/swing/JSlider.java
+++ b/javax/swing/JSlider.java
@@ -786,7 +786,7 @@ public class JSlider extends JComponent implements SwingConstants, Accessible,
{
for (Enumeration list = labelTable.elements(); list.hasMoreElements();)
{
- Object o = (JLabel) list.nextElement();
+ Object o = list.nextElement();
if (o instanceof JComponent)
{
JComponent jc = (JComponent) o;
diff --git a/javax/swing/RepaintManager.java b/javax/swing/RepaintManager.java
index afed7ec8e..773371489 100644
--- a/javax/swing/RepaintManager.java
+++ b/javax/swing/RepaintManager.java
@@ -38,6 +38,7 @@ exception statement from your version. */
package javax.swing;
+import gnu.classpath.SystemProperties;
import gnu.java.awt.LowPriorityEvent;
import java.applet.Applet;
@@ -58,6 +59,8 @@ import java.util.Iterator;
import java.util.Set;
import java.util.WeakHashMap;
+import javax.swing.text.JTextComponent;
+
/**
* <p>The repaint manager holds a set of dirty regions, invalid components,
* and a double buffer surface. The dirty regions and invalid components
@@ -261,7 +264,9 @@ public class RepaintManager
invalidComponents = new ArrayList();
repaintWorker = new RepaintWorker();
doubleBufferMaximumSize = new Dimension(2000,2000);
- doubleBufferingEnabled = true;
+ doubleBufferingEnabled =
+ SystemProperties.getProperty("gnu.swing.doublebuffering", "true")
+ .equals("true");
offscreenBuffers = new WeakHashMap();
}
@@ -426,7 +431,6 @@ public class RepaintManager
{
if (w <= 0 || h <= 0 || !component.isShowing())
return;
-
component.computeVisibleRect(rectCache);
SwingUtilities.computeIntersection(x, y, w, h, rectCache);
diff --git a/javax/swing/plaf/basic/BasicTextUI.java b/javax/swing/plaf/basic/BasicTextUI.java
index dc30347f5..d4e43c60e 100644
--- a/javax/swing/plaf/basic/BasicTextUI.java
+++ b/javax/swing/plaf/basic/BasicTextUI.java
@@ -378,7 +378,7 @@ public abstract class BasicTextUI extends TextUI
{
if (view != null)
{
- Rectangle b = s.getBounds();
+ Rectangle b = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
view.setSize(b.width, b.height);
view.paint(g, s);
}
@@ -997,6 +997,11 @@ public abstract class BasicTextUI extends TextUI
rootView.setSize(d.width - i.left - i.right,
d.height - i.top - i.bottom);
}
+ else
+ {
+ // Not laid out yet. Force some pseudo size.
+ rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
+ }
w = rootView.getPreferredSpan(View.X_AXIS);
h = rootView.getPreferredSpan(View.Y_AXIS);
}
@@ -1092,7 +1097,6 @@ public abstract class BasicTextUI extends TextUI
AbstractDocument aDoc = (AbstractDocument) doc;
aDoc.readLock();
}
-
paintSafely(g);
}
finally
@@ -1273,12 +1277,26 @@ public abstract class BasicTextUI extends TextUI
Position.Bias[] biasRet)
throws BadLocationException
{
- // A comment in the spec of NavigationFilter.getNextVisualPositionFrom()
- // suggests that this method should be implemented by forwarding the call
- // the root view.
- return rootView.getNextVisualPositionFrom(pos, b,
- getVisibleEditorRect(),
- direction, biasRet);
+ int offset = -1;
+ Document doc = textComponent.getDocument();
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ Rectangle alloc = getVisibleEditorRect();
+ if (alloc != null)
+ {
+ rootView.setSize(alloc.width, alloc.height);
+ offset = rootView.getNextVisualPositionFrom(pos, b, alloc,
+ direction, biasRet);
+ }
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ return offset;
}
/**
@@ -1388,7 +1406,25 @@ public abstract class BasicTextUI extends TextUI
*/
public int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn)
{
- return rootView.viewToModel(pt.x, pt.y, getVisibleEditorRect(), biasReturn);
+ int offset = -1;
+ Document doc = textComponent.getDocument();
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readLock();
+ try
+ {
+ Rectangle alloc = getVisibleEditorRect();
+ if (alloc != null)
+ {
+ rootView.setSize(alloc.width, alloc.height);
+ offset = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn);
+ }
+ }
+ finally
+ {
+ if (doc instanceof AbstractDocument)
+ ((AbstractDocument) doc).readUnlock();
+ }
+ return offset;
}
/**
@@ -1420,6 +1456,11 @@ public abstract class BasicTextUI extends TextUI
}
/**
+ * A cached Insets instance to be reused below.
+ */
+ private Insets cachedInsets;
+
+ /**
* Returns the allocation to give the root view.
*
* @return the allocation to give the root view
@@ -1437,7 +1478,7 @@ public abstract class BasicTextUI extends TextUI
if (width <= 0 || height <= 0)
return null;
- Insets insets = textComponent.getInsets();
+ Insets insets = textComponent.getInsets(cachedInsets);
return new Rectangle(insets.left, insets.top,
width - insets.left - insets.right,
height - insets.top - insets.bottom);
@@ -1488,4 +1529,5 @@ public abstract class BasicTextUI extends TextUI
{
// The default implementation does nothing.
}
+
}
diff --git a/javax/swing/plaf/metal/MetalIconFactory.java b/javax/swing/plaf/metal/MetalIconFactory.java
index d39fdc06b..2817336a8 100644
--- a/javax/swing/plaf/metal/MetalIconFactory.java
+++ b/javax/swing/plaf/metal/MetalIconFactory.java
@@ -1039,20 +1039,22 @@ public class MetalIconFactory implements Serializable
g.drawLine(x + 6, y + 14, x, y + 8);
g.drawLine(x, y + 7, x, y + 1);
- // Fill the icon.
- if (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme
- && enabled)
- {
- String gradient;
- if (focus)
- gradient = "Slider.focusGradient";
- else
- gradient = "Slider.gradient";
- MetalUtils.paintGradient(g, x + 1, y + 2, 12, 13,
- SwingConstants.VERTICAL, gradient,
- gradientMask);
- }
- else
+// The following is commented out until the masking for the gradient painting
+// is working correctly
+// // Fill the icon.
+// if (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme
+// && enabled)
+// {
+// String gradient;
+// if (focus)
+// gradient = "Slider.focusGradient";
+// else
+// gradient = "Slider.gradient";
+// MetalUtils.paintGradient(g, x + 1, y + 2, 12, 13,
+// SwingConstants.VERTICAL, gradient,
+// gradientMask);
+// }
+// else
{
if (focus)
g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
@@ -1700,20 +1702,22 @@ public class MetalIconFactory implements Serializable
g.drawLine(x + 8, y + 14, x + 1, y + 14);
g.drawLine(x, y + 13, x, y + 1);
- // Fill the icon.
- if (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme
- && enabled)
- {
- String gradient;
- if (focus)
- gradient = "Slider.focusGradient";
- else
- gradient = "Slider.gradient";
- MetalUtils.paintGradient(g, x + 2, y + 1, 13, 12,
- SwingConstants.HORIZONTAL, gradient,
- gradientMask);
- }
- else
+// The following is commented out until the masking for the gradient painting
+// is working correctly
+// // Fill the icon.
+// if (MetalLookAndFeel.getCurrentTheme() instanceof OceanTheme
+// && enabled)
+// {
+// String gradient;
+// if (focus)
+// gradient = "Slider.focusGradient";
+// else
+// gradient = "Slider.gradient";
+// MetalUtils.paintGradient(g, x + 2, y + 1, 13, 12,
+// SwingConstants.HORIZONTAL, gradient,
+// gradientMask);
+// }
+// else
{
if (focus)
g.setColor(MetalLookAndFeel.getPrimaryControlShadow());
diff --git a/javax/swing/text/AbstractDocument.java b/javax/swing/text/AbstractDocument.java
index 54797fdb0..76f1602f4 100644
--- a/javax/swing/text/AbstractDocument.java
+++ b/javax/swing/text/AbstractDocument.java
@@ -46,6 +46,7 @@ import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.EventListener;
+import java.util.HashMap;
import java.util.Hashtable;
import java.util.Vector;
@@ -158,14 +159,10 @@ public abstract class AbstractDocument implements Document, Serializable
private int numReaders = 0;
/**
- * Tells if there are one or more writers waiting.
+ * The number of current writers. If this is > 1 then the same thread entered
+ * the write lock more than once.
*/
- private int numWritersWaiting = 0;
-
- /**
- * A condition variable that readers and writers wait on.
- */
- private Object documentCV = new Object();
+ private int numWriters = 0;
/** An instance of a DocumentFilter.FilterBypass which allows calling
* the insert, remove and replace method without checking for an installed
@@ -315,7 +312,8 @@ public abstract class AbstractDocument implements Document, Serializable
* @throws BadLocationException if <code>offset</code> is not a valid
* location in the documents content model
*/
- public Position createPosition(final int offset) throws BadLocationException
+ public synchronized Position createPosition(final int offset)
+ throws BadLocationException
{
return content.createPosition(offset);
}
@@ -432,7 +430,7 @@ public abstract class AbstractDocument implements Document, Serializable
* @return the thread that currently modifies this <code>Document</code>
* if there is one, otherwise <code>null</code>
*/
- protected final Thread getCurrentWriter()
+ protected final synchronized Thread getCurrentWriter()
{
return currentWriter;
}
@@ -1022,25 +1020,21 @@ public abstract class AbstractDocument implements Document, Serializable
* Blocks until a read lock can be obtained. Must block if there is
* currently a writer modifying the <code>Document</code>.
*/
- public final void readLock()
+ public final synchronized void readLock()
{
- if (currentWriter != null && currentWriter.equals(Thread.currentThread()))
- return;
- synchronized (documentCV)
+ try
{
- while (currentWriter != null || numWritersWaiting > 0)
+ while (currentWriter != null)
{
-
- try
- {
- documentCV.wait();
- }
- catch (InterruptedException ie)
- {
- throw new Error("interrupted trying to get a readLock");
- }
+ if (currentWriter == Thread.currentThread())
+ return;
+ wait();
}
- numReaders++;
+ numReaders++;
+ }
+ catch (InterruptedException ex)
+ {
+ throw new Error("Interrupted during grab read lock");
}
}
@@ -1048,7 +1042,7 @@ public abstract class AbstractDocument implements Document, Serializable
* Releases the read lock. If this was the only reader on this
* <code>Document</code>, writing may begin now.
*/
- public final void readUnlock()
+ public final synchronized void readUnlock()
{
// Note we could have a problem here if readUnlock was called without a
// prior call to readLock but the specs simply warn users to ensure that
@@ -1075,21 +1069,14 @@ public abstract class AbstractDocument implements Document, Serializable
// FIXME: the reference implementation throws a
// javax.swing.text.StateInvariantError here
- if (numReaders == 0)
+ if (numReaders <= 0)
throw new IllegalStateException("document lock failure");
- synchronized (documentCV)
- {
- // If currentWriter is not null, the application code probably had a
- // writeLock and then tried to obtain a readLock, in which case
- // numReaders wasn't incremented
- if (currentWriter == null)
- {
- numReaders --;
- if (numReaders == 0 && numWritersWaiting != 0)
- documentCV.notify();
- }
- }
+ // If currentWriter is not null, the application code probably had a
+ // writeLock and then tried to obtain a readLock, in which case
+ // numReaders wasn't incremented
+ numReaders--;
+ notify();
}
/**
@@ -1113,12 +1100,21 @@ public abstract class AbstractDocument implements Document, Serializable
*/
public void remove(int offset, int length) throws BadLocationException
{
- if (documentFilter == null)
- removeImpl(offset, length);
- else
- documentFilter.remove(getBypass(), offset, length);
+ writeLock();
+ try
+ {
+ DocumentFilter f = getDocumentFilter();
+ if (f == null)
+ removeImpl(offset, length);
+ else
+ f.remove(getBypass(), offset, length);
+ }
+ finally
+ {
+ writeUnlock();
+ }
}
-
+
void removeImpl(int offset, int length) throws BadLocationException
{
// The RI silently ignores all requests that have a negative length.
@@ -1135,21 +1131,12 @@ public abstract class AbstractDocument implements Document, Serializable
new DefaultDocumentEvent(offset, length,
DocumentEvent.EventType.REMOVE);
- try
- {
- writeLock();
-
- // The order of the operations below is critical!
- removeUpdate(event);
- UndoableEdit temp = content.remove(offset, length);
-
- postRemoveUpdate(event);
- fireRemoveUpdate(event);
- }
- finally
- {
- writeUnlock();
- }
+ // The order of the operations below is critical!
+ removeUpdate(event);
+ UndoableEdit temp = content.remove(offset, length);
+
+ postRemoveUpdate(event);
+ fireRemoveUpdate(event);
}
}
@@ -1343,26 +1330,25 @@ public abstract class AbstractDocument implements Document, Serializable
* Blocks until a write lock can be obtained. Must wait if there are
* readers currently reading or another thread is currently writing.
*/
- protected final void writeLock()
+ protected synchronized final void writeLock()
{
- if (currentWriter != null && currentWriter.equals(Thread.currentThread()))
- return;
- synchronized (documentCV)
+ try
{
- numWritersWaiting++;
- while (numReaders > 0)
+ while (numReaders > 0 || currentWriter != null)
{
- try
+ if (Thread.currentThread() == currentWriter)
{
- documentCV.wait();
- }
- catch (InterruptedException ie)
- {
- throw new Error("interruped while trying to obtain write lock");
+ numWriters++;
+ return;
}
+ wait();
}
- numWritersWaiting --;
currentWriter = Thread.currentThread();
+ numWriters = 1;
+ }
+ catch (InterruptedException ex)
+ {
+ throw new Error("Interupted during grab write lock");
}
}
@@ -1370,16 +1356,14 @@ public abstract class AbstractDocument implements Document, Serializable
* Releases the write lock. This allows waiting readers or writers to
* obtain the lock.
*/
- protected final void writeUnlock()
+ protected final synchronized void writeUnlock()
{
- synchronized (documentCV)
- {
- if (Thread.currentThread().equals(currentWriter))
- {
- currentWriter = null;
- documentCV.notifyAll();
- }
- }
+ if (--numWriters <= 0)
+ {
+ numWriters = 0;
+ currentWriter = null;
+ notifyAll();
+ }
}
/**
@@ -2384,6 +2368,11 @@ public abstract class AbstractDocument implements Document, Serializable
/** The serialization UID (compatible with JDK1.5). */
private static final long serialVersionUID = 5230037221564563284L;
+ /**
+ * The threshold that indicates when we switch to using a Hashtable.
+ */
+ private static final int THRESHOLD = 10;
+
/** The starting offset of the change. */
private int offset;
@@ -2394,15 +2383,18 @@ public abstract class AbstractDocument implements Document, Serializable
private DocumentEvent.EventType type;
/**
- * Maps <code>Element</code> to their change records.
+ * Maps <code>Element</code> to their change records. This is only
+ * used when the changes array gets too big. We can use an
+ * (unsync'ed) HashMap here, since changes to this are (should) always
+ * be performed inside a write lock.
*/
- Hashtable changes;
+ private HashMap changes;
/**
* Indicates if this event has been modified or not. This is used to
* determine if this event is thrown.
*/
- boolean modified;
+ private boolean modified;
/**
* Creates a new <code>DefaultDocumentEvent</code>.
@@ -2417,7 +2409,6 @@ public abstract class AbstractDocument implements Document, Serializable
this.offset = offset;
this.length = length;
this.type = type;
- changes = new Hashtable();
modified = false;
}
@@ -2431,9 +2422,27 @@ public abstract class AbstractDocument implements Document, Serializable
public boolean addEdit(UndoableEdit edit)
{
// XXX - Fully qualify ElementChange to work around gcj bug #2499.
- if (edit instanceof DocumentEvent.ElementChange)
+
+ // Start using Hashtable when we pass a certain threshold. This
+ // gives a good memory/performance compromise.
+ if (changes == null && edits.size() > THRESHOLD)
+ {
+ changes = new HashMap();
+ int count = edits.size();
+ for (int i = 0; i < count; i++)
+ {
+ Object o = edits.elementAt(i);
+ if (o instanceof DocumentEvent.ElementChange)
+ {
+ DocumentEvent.ElementChange ec =
+ (DocumentEvent.ElementChange) o;
+ changes.put(ec.getElement(), ec);
+ }
+ }
+ }
+
+ if (changes != null && edit instanceof DocumentEvent.ElementChange)
{
- modified = true;
DocumentEvent.ElementChange elEdit =
(DocumentEvent.ElementChange) edit;
changes.put(elEdit.getElement(), elEdit);
@@ -2492,7 +2501,27 @@ public abstract class AbstractDocument implements Document, Serializable
public DocumentEvent.ElementChange getChange(Element elem)
{
// XXX - Fully qualify ElementChange to work around gcj bug #2499.
- return (DocumentEvent.ElementChange) changes.get(elem);
+ DocumentEvent.ElementChange change = null;
+ if (changes != null)
+ {
+ change = (DocumentEvent.ElementChange) changes.get(elem);
+ }
+ else
+ {
+ int count = edits.size();
+ for (int i = 0; i < count && change == null; i++)
+ {
+ Object o = edits.get(i);
+ if (o instanceof DocumentEvent.ElementChange)
+ {
+ DocumentEvent.ElementChange ec =
+ (DocumentEvent.ElementChange) o;
+ if (elem.equals(ec.getElement()))
+ change = ec;
+ }
+ }
+ }
+ return change;
}
/**
diff --git a/javax/swing/text/BoxView.java b/javax/swing/text/BoxView.java
index 962d06219..72bc07e75 100644
--- a/javax/swing/text/BoxView.java
+++ b/javax/swing/text/BoxView.java
@@ -38,6 +38,7 @@ exception statement from your version. */
package javax.swing.text;
+import java.awt.Container;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
@@ -105,6 +106,8 @@ public class BoxView
myAxis = axis;
layoutValid[0] = false;
layoutValid[1] = false;
+ requirementsValid[X_AXIS] = false;
+ requirementsValid[Y_AXIS] = false;
span[0] = 0;
span[1] = 0;
requirements[0] = new SizeRequirements();
@@ -141,7 +144,10 @@ public class BoxView
*/
public void setAxis(int axis)
{
+ boolean changed = axis != myAxis;
myAxis = axis;
+ if (changed)
+ preferenceChanged(null, true, true);
}
/**
@@ -222,35 +228,20 @@ public class BoxView
*/
public void replace(int offset, int length, View[] views)
{
- int oldNumChildren = getViewCount();
-
// Actually perform the replace.
super.replace(offset, length, views);
// Resize and copy data for cache arrays.
int newItems = views != null ? views.length : 0;
- int delta = newItems - length;
- int src = offset + length;
- int numMove = oldNumChildren - src;
- int dst = src + delta;
- offsets[X_AXIS] = replaceLayoutArray(offsets[X_AXIS], offset,
- oldNumChildren, delta, src, dst,
- numMove);
- spans[X_AXIS] = replaceLayoutArray(spans[X_AXIS], offset,
- oldNumChildren, delta, src, dst,
- numMove);
- offsets[Y_AXIS] = replaceLayoutArray(offsets[Y_AXIS], offset,
- oldNumChildren, delta, src, dst,
- numMove);
- spans[Y_AXIS] = replaceLayoutArray(spans[Y_AXIS], offset,
- oldNumChildren, delta, src, dst,
- numMove);
-
- // Invalidate layout information.
- layoutValid[X_AXIS] = false;
- requirementsValid[X_AXIS] = false;
- layoutValid[Y_AXIS] = false;
- requirementsValid[Y_AXIS] = false;
+ int minor = 1 - myAxis;
+ offsets[myAxis] = replaceLayoutArray(offsets[myAxis], offset, newItems);
+ spans[myAxis] = replaceLayoutArray(spans[myAxis], offset, newItems);
+ layoutValid[myAxis] = false;
+ requirementsValid[myAxis] = false;
+ offsets[minor] = replaceLayoutArray(offsets[minor], offset, newItems);
+ spans[minor] = replaceLayoutArray(spans[minor], offset, newItems);
+ layoutValid[minor] = false;
+ requirementsValid[minor] = false;
}
/**
@@ -261,27 +252,25 @@ public class BoxView
*
* @return the replaced array
*/
- private int[] replaceLayoutArray(int[] oldArray, int offset, int numChildren,
- int delta, int src, int dst, int numMove)
+ private int[] replaceLayoutArray(int[] oldArray, int offset, int newItems)
{
- int[] newArray;
- if (numChildren + delta > oldArray.length)
- {
- int newLength = Math.max(2 * oldArray.length, numChildren + delta);
- newArray = new int[newLength];
- System.arraycopy(oldArray, 0, newArray, 0, offset);
- System.arraycopy(oldArray, src, newArray, dst, numMove);
- }
- else
- {
- newArray = oldArray;
- System.arraycopy(newArray, src, newArray, dst, numMove);
- }
+ int num = getViewCount();
+ int[] newArray = new int[num];
+ System.arraycopy(oldArray, 0, newArray, 0, offset);
+ System.arraycopy(oldArray, offset, newArray, offset + newItems,
+ num - newItems - offset);
return newArray;
}
/**
+ * A Rectangle instance to be reused in the paint() method below.
+ */
+ private final Rectangle tmpRect = new Rectangle();
+
+ private Rectangle clipRect = new Rectangle();
+
+ /**
* Renders the <code>Element</code> that is associated with this
* <code>View</code>.
*
@@ -290,26 +279,95 @@ public class BoxView
*/
public void paint(Graphics g, Shape a)
{
- Rectangle alloc;
- if (a instanceof Rectangle)
- alloc = (Rectangle) a;
- else
- alloc = a.getBounds();
-
- int x = alloc.x + getLeftInset();
- int y = alloc.y + getTopInset();
+ // Try to avoid allocation if possible (almost all cases).
+ Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+
+ // This returns a cached instance.
+ alloc = getInsideAllocation(alloc);
+
+ // The following algorithm optimizes painting of a BoxView by taking
+ // advantage of the layout order of the box children.
+ //
+ // 1. It first searches a child that which's allocation is inside the clip.
+ // This is accomplished by an efficient binary search. This assumes
+ // that the children of the BoxView are laid out in the same order
+ // as their index within the view. This is true for the BoxView, but
+ // might not be the case for all subclasses.
+ // 2. Starting from the found view, it paints the children in both
+ // directions until the first view is hit that is outside the clip.
+
+ // First we search a child view that is inside the clip.
+
+ // Fetch the clip rect and calculate the center point of it.
+ clipRect = g.getClipBounds(clipRect);
+ int cX = clipRect.x + clipRect.width / 2;
+ int cY = clipRect.y + clipRect.height / 2;
+
+ int viewCount = getViewCount();
+ int up = viewCount;
+ int low = 0;
+ int mid = (up - low) / 2;
+ View start = getView(mid);
+
+ int newMid;
+ // Use another cached instance here to avoid allocations during
+ // painting.
+ tmpRect.setBounds(alloc);
+ // This modifies tmpRect.
+ childAllocation(mid, tmpRect);
+ while (! clipRect.intersects(tmpRect))
+ {
+ if (isBefore(cX, cY, tmpRect))
+ {
+ up = mid;
+ newMid = (up - low) / 2 + low;
+ mid = (newMid == mid) ? newMid - 1 : newMid;
+ }
+ else if (isAfter(cX, cY, tmpRect))
+ {
+ low = mid;
+ newMid = (up - low) / 2 + low;
+ mid = (newMid == mid) ? newMid + 1 : newMid;
+ }
+ else
+ break;
+ if (mid >= 0 && mid < viewCount)
+ {
+ start = getView(mid);
+ tmpRect.setBounds(alloc);
+ childAllocation(mid, tmpRect);
+ }
+ else
+ break;
+ }
- Rectangle clip = g.getClipBounds();
- Rectangle tmp = new Rectangle();
- int count = getViewCount();
- for (int i = 0; i < count; ++i)
+ if (mid >= 0 && mid < viewCount)
{
- tmp.x = x + getOffset(X_AXIS, i);
- tmp.y = y + getOffset(Y_AXIS, i);
- tmp.width = getSpan(X_AXIS, i);
- tmp.height = getSpan(Y_AXIS, i);
- if (tmp.intersects(clip))
- paintChild(g, tmp, i);
+ // Ok, we found one view that is inside the clip rect. Now paint the
+ // children before it that are inside the clip.
+ boolean inClip = true;
+ for (int i = mid - 1; i >= 0 && inClip; i--)
+ {
+ start = getView(i);
+ tmpRect.setBounds(alloc);
+ childAllocation(i, tmpRect);
+ inClip = clipRect.intersects(tmpRect);
+ if (inClip)
+ paintChild(g, tmpRect, i);
+ }
+
+ // Now paint the found view and all views after it that lie inside the
+ // clip.
+ inClip = true;
+ for (int i = mid; i < viewCount && inClip; i++)
+ {
+ start = getView(i);
+ tmpRect.setBounds(alloc);
+ childAllocation(i, tmpRect);
+ inClip = clipRect.intersects(tmpRect);
+ if (inClip)
+ paintChild(g, tmpRect, i);
+ }
}
}
@@ -742,49 +800,32 @@ public class BoxView
*/
protected void layout(int width, int height)
{
- int[] newSpan = new int[]{ width, height };
- int count = getViewCount();
-
- // Update minor axis as appropriate. We need to first update the minor
- // axis layout because that might affect the children's preferences along
- // the major axis.
- int minorAxis = myAxis == X_AXIS ? Y_AXIS : X_AXIS;
- if ((! isLayoutValid(minorAxis)) || newSpan[minorAxis] != span[minorAxis])
- {
- layoutValid[minorAxis] = false;
- span[minorAxis] = newSpan[minorAxis];
- layoutMinorAxis(span[minorAxis], minorAxis, offsets[minorAxis],
- spans[minorAxis]);
-
- // Update the child view's sizes.
- for (int i = 0; i < count; ++i)
- {
- getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
- }
- layoutValid[minorAxis] = true;
- }
-
+ layoutAxis(X_AXIS, width);
+ layoutAxis(Y_AXIS, height);
+ }
- // Update major axis as appropriate.
- if ((! isLayoutValid(myAxis)) || newSpan[myAxis] != span[myAxis])
+ private void layoutAxis(int axis, int s)
+ {
+ if (span[axis] != s)
+ layoutValid[axis] = false;
+ if (! layoutValid[axis])
{
- layoutValid[myAxis] = false;
- span[myAxis] = newSpan[myAxis];
- layoutMajorAxis(span[myAxis], myAxis, offsets[myAxis],
- spans[myAxis]);
+ span[axis] = s;
+ updateRequirements(axis);
+ if (axis == myAxis)
+ layoutMajorAxis(span[axis], axis, offsets[axis], spans[axis]);
+ else
+ layoutMinorAxis(span[axis], axis, offsets[axis], spans[axis]);
+ layoutValid[axis] = true;
- // Update the child view's sizes.
- for (int i = 0; i < count; ++i)
+ // Push out child layout.
+ int viewCount = getViewCount();
+ for (int i = 0; i < viewCount; i++)
{
- getView(i).setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
+ View v = getView(i);
+ v.setSize(spans[X_AXIS][i], spans[Y_AXIS][i]);
}
- layoutValid[myAxis] = true;
}
-
- if (layoutValid[myAxis] == false)
- System.err.println("WARNING: Major axis layout must be valid after layout");
- if (layoutValid[minorAxis] == false)
- System.err.println("Minor axis layout must be valid after layout");
}
/**
@@ -807,7 +848,7 @@ public class BoxView
{
View child = getView(i);
spans[i] = (int) child.getPreferredSpan(axis);
- sumPref = spans[i];
+ sumPref += spans[i];
}
// Try to adjust the spans so that we fill the targetSpan.
@@ -1048,9 +1089,11 @@ public class BoxView
{
if (axis != X_AXIS && axis != Y_AXIS)
throw new IllegalArgumentException("Illegal axis argument");
- int weight = 1;
- if (axis == myAxis)
- weight = 0;
+ updateRequirements(axis);
+ int weight = 0;
+ if ((requirements[axis].preferred != requirements[axis].minimum)
+ || (requirements[axis].preferred != requirements[axis].maximum))
+ weight = 1;
return weight;
}
@@ -1077,8 +1120,30 @@ public class BoxView
protected void forwardUpdate(DocumentEvent.ElementChange ec, DocumentEvent e,
Shape a, ViewFactory vf)
{
- // FIXME: What to do here?
+ boolean wasValid = isLayoutValid(myAxis);
super.forwardUpdate(ec, e, a, vf);
+ // Trigger repaint when one of the children changed the major axis.
+ if (wasValid && ! isLayoutValid(myAxis))
+ {
+ Container c = getContainer();
+ if (a != null && c != null)
+ {
+ int pos = e.getOffset();
+ int index = getViewIndexAtPosition(pos);
+ Rectangle r = getInsideAllocation(a);
+ if (myAxis == X_AXIS)
+ {
+ r.x += offsets[myAxis][index];
+ r.width -= offsets[myAxis][index];
+ }
+ else
+ {
+ r.y += offsets[myAxis][index];
+ r.height -= offsets[myAxis][index];
+ }
+ c.repaint(r.x, r.y, r.width, r.height);
+ }
+ }
}
public int viewToModel(float x, float y, Shape a, Position.Bias[] bias)
diff --git a/javax/swing/text/CompositeView.java b/javax/swing/text/CompositeView.java
index d4467f314..570fc955c 100644
--- a/javax/swing/text/CompositeView.java
+++ b/javax/swing/text/CompositeView.java
@@ -68,7 +68,7 @@ public abstract class CompositeView
* initialized in {@link #getInsideAllocation} and reused and modified in
* {@link #childAllocation(int, Rectangle)}.
*/
- Rectangle insideAllocation;
+ private final Rectangle insideAllocation = new Rectangle();
/**
* The insets of this <code>CompositeView</code>. This is initialized
@@ -282,12 +282,12 @@ public abstract class CompositeView
}
}
}
- else
- {
- throw new BadLocationException("Position " + pos
- + " is not represented by view.", pos);
- }
}
+
+ if (ret == null)
+ throw new BadLocationException("Position " + pos
+ + " is not represented by view.", pos);
+
return ret;
}
@@ -527,24 +527,17 @@ public abstract class CompositeView
if (a == null)
return null;
- Rectangle alloc = a.getBounds();
+ // Try to avoid allocation of Rectangle here.
+ Rectangle alloc = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+
// Initialize the inside allocation rectangle. This is done inside
// a synchronized block in order to avoid multiple threads creating
// this instance simultanously.
- Rectangle inside;
- synchronized(this)
- {
- inside = insideAllocation;
- if (inside == null)
- {
- inside = new Rectangle();
- insideAllocation = inside;
- }
- }
- inside.x = alloc.x + left;
- inside.y = alloc.y + top;
- inside.width = alloc.width - left - right;
- inside.height = alloc.height - top - bottom;
+ Rectangle inside = insideAllocation;
+ inside.x = alloc.x + getLeftInset();
+ inside.y = alloc.y + getTopInset();
+ inside.width = alloc.width - getLeftInset() - getRightInset();
+ inside.height = alloc.height - getTopInset() - getBottomInset();
return inside;
}
diff --git a/javax/swing/text/DefaultStyledDocument.java b/javax/swing/text/DefaultStyledDocument.java
index 367666053..3156ca67f 100644
--- a/javax/swing/text/DefaultStyledDocument.java
+++ b/javax/swing/text/DefaultStyledDocument.java
@@ -683,6 +683,58 @@ public class DefaultStyledDocument extends AbstractDocument implements
return ret;
}
+ /**
+ * Creates a document in response to a call to
+ * {@link DefaultStyledDocument#create(ElementSpec[])}.
+ *
+ * @param len the length of the inserted text
+ * @param data the specs for the elements
+ * @param ev the document event
+ */
+ void create(int len, ElementSpec[] data, DefaultDocumentEvent ev)
+ {
+ prepareEdit(offset, len);
+ Element el = root;
+ int index = el.getElementIndex(0);
+ while (! el.isLeaf())
+ {
+ Element child = el.getElement(index);
+ Edit edit = new Edit(el, index, false);
+ elementStack.push(edit);
+ el = child;
+ index = el.getElementIndex(0);
+ }
+ Edit ed = (Edit) elementStack.peek();
+ Element child = ed.e.getElement(ed.index);
+ ed.added.add(createLeafElement(ed.e, child.getAttributes(), getLength(),
+ child.getEndOffset()));
+ ed.removed.add(child);
+ while (elementStack.size() > 1)
+ pop();
+ int n = data.length;
+
+ // Reset root element's attributes.
+ AttributeSet newAtts = null;
+ if (n > 0 && data[0].getType() == ElementSpec.StartTagType)
+ newAtts = data[0].getAttributes();
+ if (newAtts == null)
+ newAtts = SimpleAttributeSet.EMPTY;
+ MutableAttributeSet mAtts = (MutableAttributeSet) root.getAttributes();
+ ev.addEdit(new AttributeUndoableEdit(root, newAtts, true));
+ mAtts.removeAttributes(mAtts);
+ mAtts.addAttributes(newAtts);
+
+ // Insert the specified elements.
+ for (int i = 1; i < n; i++)
+ insertElement(data[i]);
+
+ // Pop remaining stack.
+ while (elementStack.size() > 0)
+ pop();
+
+ finishEdit(ev);
+ }
+
private boolean canJoin(Element e0, Element e1)
{
boolean ret = false;
@@ -987,6 +1039,8 @@ public class DefaultStyledDocument extends AbstractDocument implements
ElementEdit ee = new ElementEdit(parent, index, removed, added);
ev.addEdit(ee);
}
+ edits.clear();
+ elementStack.clear();
}
/**
@@ -1034,7 +1088,7 @@ public class DefaultStyledDocument extends AbstractDocument implements
createFracture(data);
i = 0;
}
-
+
// Handle each ElementSpec individually.
for (; i < data.length; i++)
{
@@ -1069,14 +1123,13 @@ public class DefaultStyledDocument extends AbstractDocument implements
if (offset == 0 && fracturedParent != null
&& data[0].getType() == ElementSpec.EndTagType)
{
- for (int p = 0;
+ int p;
+ for (p = 0;
p < data.length && data[p].getType() == ElementSpec.EndTagType;
- p++)
- {
- Edit edit = insertPath[insertPath.length - p - 1];
- edit.index--;
- edit.removed.add(0, edit.e.getElement(edit.index));
- }
+ p++);
+ Edit edit = insertPath[insertPath.length - p - 1];
+ edit.index--;
+ edit.removed.add(0, edit.e.getElement(edit.index));
}
}
@@ -2379,18 +2432,24 @@ public class DefaultStyledDocument extends AbstractDocument implements
if (length == 0)
return;
- UndoableEdit edit = content.insertString(offset,
- contentBuffer.toString());
+ Content c = getContent();
+ UndoableEdit edit = c.insertString(offset,
+ contentBuffer.toString());
// Create the DocumentEvent with the ElementEdit added
DefaultDocumentEvent ev = new DefaultDocumentEvent(offset,
length,
DocumentEvent.EventType.INSERT);
+
ev.addEdit(edit);
// Finally we must update the document structure and fire the insert
// update event.
buffer.insert(offset, length, data, ev);
+
+ super.insertUpdate(ev, null);
+
+ ev.end();
fireInsertUpdate(ev);
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
}
@@ -2410,14 +2469,16 @@ public class DefaultStyledDocument extends AbstractDocument implements
*/
protected void create(ElementSpec[] data)
{
- writeLock();
try
{
+
// Clear content if there is some.
int len = getLength();
if (len > 0)
remove(0, len);
+ writeLock();
+
// Now we insert the content.
StringBuilder b = new StringBuilder();
for (int i = 0; i < data.length; ++i)
@@ -2429,38 +2490,18 @@ public class DefaultStyledDocument extends AbstractDocument implements
Content content = getContent();
UndoableEdit cEdit = content.insertString(0, b.toString());
+ len = b.length();
DefaultDocumentEvent ev =
new DefaultDocumentEvent(0, b.length(),
DocumentEvent.EventType.INSERT);
ev.addEdit(cEdit);
- // We do a little trick here to get the new structure: We instantiate
- // a new ElementBuffer with a new root element, insert into that root
- // and then reparent the newly created elements to the old root
- // element.
- BranchElement createRoot =
- (BranchElement) createBranchElement(null, null);
- Element dummyLeaf = createLeafElement(createRoot, null, 0, 1);
- createRoot.replace(0, 0, new Element[]{ dummyLeaf });
- ElementBuffer createBuffer = new ElementBuffer(createRoot);
- createBuffer.insert(0, b.length(), data, new DefaultDocumentEvent(0, b.length(), DocumentEvent.EventType.INSERT));
- // Now the new root is the first child of the createRoot.
- Element newRoot = createRoot.getElement(0);
- BranchElement root = (BranchElement) getDefaultRootElement();
- Element[] added = new Element[newRoot.getElementCount()];
- for (int i = 0; i < added.length; ++i)
- {
- added[i] = newRoot.getElement(i);
- ((AbstractElement) added[i]).element_parent = root;
- }
- Element[] removed = new Element[root.getElementCount()];
- for (int i = 0; i < removed.length; ++i)
- removed[i] = root.getElement(i);
+ buffer.create(len, data, ev);
- // Replace the old elements in root with the new and update the event.
- root.replace(0, removed.length, added);
- ev.addEdit(new ElementEdit(root, 0, removed, added));
+ // For the bidi update.
+ super.insertUpdate(ev, null);
+ ev.end();
fireInsertUpdate(ev);
fireUndoableEditUpdate(new UndoableEditEvent(this, ev));
}
diff --git a/javax/swing/text/FlowView.java b/javax/swing/text/FlowView.java
index 9609f3fc8..c2bed399f 100644
--- a/javax/swing/text/FlowView.java
+++ b/javax/swing/text/FlowView.java
@@ -488,7 +488,7 @@ public abstract class FlowView extends BoxView
if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE)
>= ForcedBreakWeight)
{
- max = Math.max(pref, pref);
+ max = Math.max(max, pref);
pref = 0;
}
}
diff --git a/javax/swing/text/GapContent.java b/javax/swing/text/GapContent.java
index 990e9d464..08a318d8b 100644
--- a/javax/swing/text/GapContent.java
+++ b/javax/swing/text/GapContent.java
@@ -39,16 +39,13 @@ exception statement from your version. */
package javax.swing.text;
import java.io.Serializable;
-import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import java.util.Set;
import java.util.Vector;
-import java.util.WeakHashMap;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
@@ -71,7 +68,7 @@ public class GapContent
/**
* A {@link Position} implementation for <code>GapContent</code>.
*/
- private class GapContentPosition
+ class GapContentPosition
implements Position
{
@@ -82,39 +79,6 @@ public class GapContent
Mark mark;
/**
- * Creates a new GapContentPosition object.
- *
- * @param offset the offset of this Position
- */
- GapContentPosition(int offset)
- {
- // Try to find the mark in the positionMarks array, and store the index
- // to it.
- synchronized (GapContent.this)
- {
- // Try to make space.
- garbageCollect();
- Mark m = new Mark(offset);
- int i = search(marks, m);
- if (i >= 0) // mark found
- {
- m = (Mark) marks.get(i);
- }
- else
- {
- i = -i - 1;
- marks.add(i, m);
- }
- m.refCount++;
- mark = m;
- }
-
- // Register this position in the death queue, so we can cleanup the marks
- // when this position object gets GC'ed.
- new WeakReference(this, queueOfDeath);
- }
-
- /**
* Returns the current offset of this Position within the content.
*
* @return the current offset of this Position within the content.
@@ -133,7 +97,7 @@ public class GapContent
* be garbage collected while we still hold a reference to the Mark object.
*/
private class Mark
- implements Comparable
+ extends WeakReference
{
/**
* The actual mark into the buffer.
@@ -141,21 +105,20 @@ public class GapContent
int mark;
/**
- * The number of GapContentPosition object that reference this mark. If
- * it reaches zero, it get's deleted by {@link GapContent#garbageCollect()}.
- */
- int refCount;
-
- /**
* Creates a new Mark object for the specified offset.
*
* @param offset the offset
*/
Mark(int offset)
{
+ super(null);
+ mark = offset;
+ }
+
+ Mark(int offset, GapContentPosition pos, ReferenceQueue queue)
+ {
+ super(pos, queue);
mark = offset;
- if (mark >= gapStart && mark != 0)
- mark += (gapEnd - gapStart);
}
/**
@@ -165,34 +128,23 @@ public class GapContent
*/
int getOffset()
{
- assert mark == 0 || mark <= gapStart || mark >= gapEnd :
- "Invalid mark: " + mark + ", gapStart: " + gapStart
- + ", gapEnd: " + gapEnd;
-
int res = mark;
- if (mark >= gapEnd)
+ if (mark >= gapStart)
res -= (gapEnd - gapStart);
- return res;
+ return Math.max(0, res);
}
/**
- * Implementation of Comparable.
- */
- public int compareTo(Object o)
- {
- Mark other = (Mark) o;
- return mark - other.mark;
- }
- /**
- * Adjustment for equals().
+ * Returns the GapContentPosition that is associated ith this mark.
+ * This fetches the weakly referenced position object.
+ *
+ * @return the GapContentPosition that is associated ith this mark
*/
- public boolean equals(Object o)
+ GapContentPosition getPosition()
{
- if (o == null || !(o instanceof Mark))
- return false;
- else
- return ((Mark) o).mark == mark;
+ return (GapContentPosition) get();
}
+
}
/**
@@ -367,7 +319,15 @@ public class GapContent
*/
ArrayList marks;
- WeakHashMap positions;
+ /**
+ * The number of unused marks.
+ */
+ private int garbageMarks;
+
+ /**
+ * A 'static' mark that is used for searching.
+ */
+ private Mark searchMark = new Mark(0);
/**
* Queues all references to GapContentPositions that are about to be
@@ -398,7 +358,6 @@ public class GapContent
gapStart = 1;
gapEnd = size;
buffer[0] = '\n';
- positions = new WeakHashMap();
marks = new ArrayList();
queueOfDeath = new ReferenceQueue();
}
@@ -612,27 +571,33 @@ public class GapContent
// and luckily enough the GapContent can very well deal with offsets
// outside the buffer bounds. So I removed that check.
+ // First do some garbage collections.
+ while (queueOfDeath.poll() != null)
+ garbageMarks++;
+ if (garbageMarks > Math.max(5, marks.size() / 10))
+ garbageCollect();
+
// We try to find a GapContentPosition at the specified offset and return
// that. Otherwise we must create a new one.
- GapContentPosition pos = null;
- Set positionSet = positions.keySet();
- for (Iterator i = positionSet.iterator(); i.hasNext();)
- {
- GapContentPosition p = (GapContentPosition) i.next();
- if (p.getOffset() == offset)
- {
- pos = p;
- break;
- }
- }
-
- // If none was found, then create and return a new one.
- if (pos == null)
+ Mark m;
+ GapContentPosition pos;
+ int index = offset;
+ if (offset >= gapStart)
+ index += (gapEnd - gapStart);
+ searchMark.mark = index;
+ int insertIndex = search(searchMark);
+ if (!(insertIndex < marks.size()
+ && (m = (Mark) marks.get(insertIndex)).mark == index
+ && (pos = m.getPosition()) != null))
{
- pos = new GapContentPosition(offset);
- positions.put(pos, null);
+ // Create new position if none was found.
+ pos = new GapContentPosition();
+ m = new Mark(index, pos, queueOfDeath);
+ pos.mark = m;
+ marks.add(insertIndex, m);
}
-
+ // Otherwise use the found position.
+
return pos;
}
@@ -649,18 +614,29 @@ public class GapContent
assert newSize > (gapEnd - gapStart) : "The new gap size must be greater "
+ "than the old gap size";
- int delta = newSize - gapEnd + gapStart;
- // Update the marks after the gapEnd.
- adjustPositionsInRange(gapEnd, -1, delta);
+ int oldEnd = getGapEnd();
+ int oldSize = getArrayLength();
+ int upper = oldSize - oldEnd;
+ int size = (newSize + 1) * 2;
+ int newEnd = size - upper;
// Copy the data around.
- char[] newBuf = (char[]) allocateArray(length() + newSize);
- System.arraycopy(buffer, 0, newBuf, 0, gapStart);
- System.arraycopy(buffer, gapEnd, newBuf, gapStart + newSize, buffer.length
- - gapEnd);
- gapEnd = gapStart + newSize;
+ char[] newBuf = (char[]) allocateArray(size);
+ System.arraycopy(buffer, 0, newBuf, 0, Math.min(size, oldSize));
buffer = newBuf;
-
+ gapEnd = newEnd;
+ if (upper != 0)
+ System.arraycopy(buffer, oldEnd, buffer, newEnd, upper);
+
+ // Adjust marks.
+ int delta = gapEnd - oldEnd;
+ int adjIndex = searchFirst(oldEnd);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ m.mark += delta;
+ }
}
/**
@@ -670,28 +646,44 @@ public class GapContent
*/
protected void shiftGap(int newGapStart)
{
- if (newGapStart == gapStart)
- return;
- int newGapEnd = newGapStart + gapEnd - gapStart;
- if (newGapStart < gapStart)
+ int oldStart = gapStart;
+ int delta = newGapStart - oldStart;
+ int oldEnd = gapEnd;
+ int newGapEnd = oldEnd + delta;
+ int size = oldEnd - oldStart;
+
+ // Shift gap in array.
+ gapStart = newGapStart;
+ gapEnd = newGapEnd;
+ if (delta > 0)
+ System.arraycopy(buffer, oldEnd, buffer, oldStart, delta);
+ else
+ System.arraycopy(buffer, newGapStart, buffer, newGapEnd, -delta);
+
+ // Adjust marks.
+ if (delta > 0)
{
- // Update the positions between newGapStart and (old) gapStart. The marks
- // must be shifted by (gapEnd - gapStart).
- adjustPositionsInRange(newGapStart, gapStart, gapEnd - gapStart);
- System.arraycopy(buffer, newGapStart, buffer, newGapEnd, gapStart
- - newGapStart);
- gapStart = newGapStart;
- gapEnd = newGapEnd;
+ int adjIndex = searchFirst(oldStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= newGapEnd)
+ break;
+ m.mark -= size;
+ }
}
- else
+ else if (delta < 0)
{
- // Update the positions between newGapEnd and (old) gapEnd. The marks
- // must be shifted by (gapEnd - gapStart).
- adjustPositionsInRange(gapEnd, newGapEnd, -(gapEnd - gapStart));
- System.arraycopy(buffer, gapEnd, buffer, gapStart, newGapStart
- - gapStart);
- gapStart = newGapStart;
- gapEnd = newGapEnd;
+ int adjIndex = searchFirst(newGapStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= oldEnd)
+ break;
+ m.mark += size;
+ }
}
resetMarksAtZero();
}
@@ -711,7 +703,18 @@ public class GapContent
assert newGapStart < gapStart : "The new gap start must be less than the "
+ "old gap start.";
- setPositionsInRange(newGapStart, gapStart, false);
+
+ // Adjust positions.
+ int adjIndex = searchFirst(newGapStart);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark > gapStart)
+ break;
+ m.mark = gapEnd;
+ }
+
gapStart = newGapStart;
resetMarksAtZero();
}
@@ -731,7 +734,19 @@ public class GapContent
assert newGapEnd > gapEnd : "The new gap end must be greater than the "
+ "old gap end.";
- setPositionsInRange(gapEnd, newGapEnd, false);
+
+ // Adjust marks.
+ int adjIndex = searchFirst(gapEnd);
+ int count = marks.size();
+ for (int i = adjIndex; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.mark >= newGapEnd)
+ break;
+ m.mark = newGapEnd;
+ }
+
+
gapEnd = newGapEnd;
resetMarksAtZero();
}
@@ -757,23 +772,88 @@ public class GapContent
protected void replace(int position, int rmSize, Object addItems,
int addSize)
{
- if (gapStart != position)
- shiftGap(position);
-
- // Remove content
- if (rmSize > 0)
- shiftGapEndUp(gapEnd + rmSize);
+ if (addSize == 0)
+ {
+ removeImpl(position, rmSize);
+ return;
+ }
+ else if (rmSize > addSize)
+ {
+ removeImpl(position + addSize, rmSize - addSize);
+ }
+ else
+ {
+ int endSize = addSize - rmSize;
+ int end = addImpl(position + rmSize, endSize);
+ System.arraycopy(addItems, rmSize, buffer, end, endSize);
+ addSize = rmSize;
+ }
+ System.arraycopy(addItems, 0, buffer, position, addSize);
+ }
+
+ /**
+ * Adjusts the positions and gap in response to a remove operation.
+ *
+ * @param pos the position at which to remove
+ * @param num the number of removed items
+ */
+ private void removeImpl(int pos, int num)
+ {
+ if (num > 0)
+ {
+ int end = pos + num;
+ int newGapSize = (gapEnd - gapStart) + num;
+ if (end <= gapStart)
+ {
+ if (gapStart != end)
+ {
+ shiftGap(end);
+ }
+ shiftGapStartDown(gapStart - num);
+ }
+ else if (pos >= gapStart)
+ {
+ if (gapStart != pos)
+ {
+ shiftGap(pos);
+ }
+ shiftGapEndUp(gapStart + newGapSize);
+ }
+ else
+ {
+ shiftGapStartDown(pos);
+ shiftGapEndUp(gapStart + newGapSize);
+ }
+ }
+ }
- // If gap is too small, enlarge the gap.
- if ((gapEnd - gapStart) <= addSize)
- shiftEnd((addSize - gapEnd + gapStart + 1) * 2 + gapEnd + DEFAULT_BUFSIZE);
+ /**
+ * Adjusts the positions and gap in response to an add operation.
+ *
+ * @param pos the position at which to add
+ * @param num the number of added items
+ *
+ * @return the adjusted position
+ */
+ private int addImpl(int pos, int num)
+ {
+ int size = gapEnd - gapStart;
+ if (num == 0)
+ {
+ if (pos > gapStart)
+ pos += size;
+ return pos;
+ }
- // Add new items to the buffer.
- if (addItems != null)
+ shiftGap(pos);
+ if (num >= size)
{
- System.arraycopy(addItems, 0, buffer, gapStart, addSize);
- gapStart += addSize;
+ shiftEnd(getArrayLength() - size + num);
+ size = gapEnd - gapStart;
}
+
+ gapStart += num;
+ return pos;
}
/**
@@ -808,95 +888,34 @@ public class GapContent
*/
protected Vector getPositionsInRange(Vector v, int offset, int length)
{
- Vector res = v;
- if (res == null)
- res = new Vector();
-
- int endOffs = offset + length;
-
- Set positionSet = positions.keySet();
- for (Iterator i = positionSet.iterator(); i.hasNext();)
+ int end = offset + length;
+ int startIndex;
+ int endIndex;
+ if (offset < gapStart)
{
- GapContentPosition p = (GapContentPosition) i.next();
- int offs = p.getOffset();
- if (offs >= offset && offs <= endOffs)
- res.add(new UndoPosRef(p.mark));
+ if (offset == 0)
+ startIndex = 0;
+ else
+ startIndex = searchFirst(offset);
+ if (end >= gapStart)
+ endIndex = searchFirst(end + (gapEnd - gapStart) + 1);
+ else
+ endIndex = searchFirst(end + 1);
}
-
- return res;
- }
-
- /**
- * Crunches all positions in the specified range to either the start or
- * end of that interval. The interval boundaries are meant to be inclusive
- * [start, end].
- *
- * @param start the start offset of the range
- * @param end the end offset of the range
- * @param toStart a boolean indicating if the positions should be crunched
- * to the start (true) or to the end (false)
- */
- private void setPositionsInRange(int start, int end, boolean toStart)
- {
- synchronized (this)
+ else
{
- // Find the start and end indices in the positionMarks array.
- Mark m = new Mark(0); // For comparison / search only.
- m.mark = start;
- int startIndex = search(marks, m);
- if (startIndex < 0) // Translate to insertion index, if not found.
- startIndex = - startIndex - 1;
- m.mark = end;
- int endIndex = search(marks, m);
- if (endIndex < 0) // Translate to insertion index - 1, if not found.
- endIndex = - endIndex - 2;
-
- // Actually adjust the marks.
- for (int i = startIndex; i <= endIndex; i++)
- ((Mark) marks.get(i)).mark = toStart ? start : end;
+ startIndex = searchFirst(offset + (gapEnd - gapStart));
+ endIndex = searchFirst(end + (gapEnd - gapStart) + 1);
}
-
- }
-
- /**
- * Adjusts the mark of all <code>Position</code>s that are in the range
- * specified by <code>offset</code> and </code>length</code> within
- * the buffer array by <code>increment</code>
- *
- * @param startOffs the start offset of the range to search
- * @param endOffs the length of the range to search, -1 means all to the end
- * @param incr the increment
- */
- private void adjustPositionsInRange(int startOffs, int endOffs, int incr)
- {
- synchronized (this)
+ if (v == null)
+ v = new Vector();
+ for (int i = startIndex; i < endIndex; i++)
{
- // Find the start and end indices in the positionMarks array.
- Mark m = new Mark(0); // For comparison / search only.
-
- m.mark = startOffs;
- int startIndex = search(marks, m);
- if (startIndex < 0) // Translate to insertion index, if not found.
- startIndex = - startIndex - 1;
-
- m.mark = endOffs;
- int endIndex;
- if (endOffs == -1)
- endIndex = marks.size() - 1;
- else
- {
- endIndex = search(marks, m);
- if (endIndex < 0) // Translate to insertion index - 1, if not found.
- endIndex = - endIndex - 2;
- }
- // Actually adjust the marks.
- for (int i = startIndex; i <= endIndex; i++) {
- ((Mark) marks.get(i)).mark += incr;
- }
+ v.add(new UndoPosRef((Mark) marks.get(i)));
}
-
+ return v;
}
-
+
/**
* Resets all <code>Position</code> that have an offset of <code>0</code>,
* to also have an array index of <code>0</code>. This might be necessary
@@ -977,30 +996,6 @@ public class GapContent
}
/**
- * Polls the queue of death for GapContentPositions, updates the
- * corresponding reference count and removes the corresponding mark
- * if the refcount reaches zero.
- *
- * This is package private to avoid accessor synthetic methods.
- */
- void garbageCollect()
- {
- Reference ref = queueOfDeath.poll();
- while (ref != null)
- {
- if (ref != null)
- {
- GapContentPosition pos = (GapContentPosition) ref.get();
- Mark m = pos.mark;
- m.refCount--;
- if (m.refCount == 0)
- marks.remove(m);
- }
- ref = queueOfDeath.poll();
- }
- }
-
- /**
* Searches the first occurance of object <code>o</code> in list
* <code>l</code>. This performs a binary search by calling
* {@link Collections#binarySearch(List, Object)} and when an object has been
@@ -1008,22 +1003,93 @@ public class GapContent
* list. The meaning of the return value is the same as in
* <code>Collections.binarySearch()</code>.
*
- * @param l the list to search through
* @param o the object to be searched
*
* @return the index of the first occurance of o in l, or -i + 1 if not found
*/
- int search(List l, Object o)
+ int search(Mark o)
{
- int i = Collections.binarySearch(l, o);
- while (i > 0)
+ int foundInd = 0;
+ boolean found = false;
+ int low = 0;
+ int up = marks.size() - 1;
+ int mid = 0;
+ if (up > -1)
{
- Object o2 = l.get(i - 1);
- if (o2.equals(o))
- i--;
+ int cmp = 0;
+ Mark last = (Mark) marks.get(up);
+ cmp = compare(o, last);
+ if (cmp > 0)
+ {
+ foundInd = up + 1;
+ found = true;
+ }
else
+ {
+ while (low <= up && ! found)
+ {
+ mid = low + (up - low) / 2;
+ Mark m = (Mark) marks.get(mid);
+ cmp = compare(o, m);
+ if (cmp == 0)
+ {
+ foundInd = mid;
+ found = true;
+ }
+ else if (cmp < 0)
+ up = mid - 1;
+ else
+ low = mid + 1;
+ }
+
+ if (! found)
+ foundInd = cmp < 0 ? mid : mid + 1;
+ }
+ }
+ return foundInd;
+ }
+
+ private int searchFirst(int index)
+ {
+ searchMark.mark = Math.max(index, 1);
+ int i = search(searchMark);
+ for (int j = i - 1; j >= 0; j--)
+ {
+ Mark m = (Mark) marks.get(j);
+ if (m.mark != index)
break;
+ i--;
}
return i;
}
+
+ /**
+ * Compares two marks.
+ *
+ * @param m1 the first mark
+ * @param m2 the second mark
+ *
+ * @return negative when m1 < m2, positive when m1 > m2 and 0 when equal
+ */
+ private int compare(Mark m1, Mark m2)
+ {
+ return m1.mark - m2.mark;
+ }
+
+ /**
+ * Collects and frees unused marks.
+ */
+ private void garbageCollect()
+ {
+ int count = marks.size();
+ ArrayList clean = new ArrayList();
+ for (int i = 0; i < count; i++)
+ {
+ Mark m = (Mark) marks.get(i);
+ if (m.get() != null)
+ clean.add(m);
+ }
+ marks = clean;
+ garbageMarks = 0;
+ }
}
diff --git a/javax/swing/text/GlyphView.java b/javax/swing/text/GlyphView.java
index 35c8dd5d7..d5070a6a9 100644
--- a/javax/swing/text/GlyphView.java
+++ b/javax/swing/text/GlyphView.java
@@ -278,44 +278,27 @@ public class GlyphView extends View implements TabableView, Cloneable
public void paint(GlyphView view, Graphics g, Shape a, int p0,
int p1)
{
- Color oldColor = g.getColor();
- int height = (int) getHeight(view);
+ updateFontMetrics(view);
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ TabExpander tabEx = view.getTabExpander();
Segment txt = view.getText(p0, p1);
- Rectangle bounds = a.getBounds();
- TabExpander tabEx = null;
- View parent = view.getParent();
- if (parent instanceof TabExpander)
- tabEx = (TabExpander) parent;
-
- int width = Utilities.getTabbedTextWidth(txt, g.getFontMetrics(),
- bounds.x, tabEx, txt.offset);
- // Fill the background of the text run.
- Color background = view.getBackground();
- if (background != null)
- {
- g.setColor(background);
- g.fillRect(bounds.x, bounds.y, width, height);
- }
- // Draw the actual text.
- g.setColor(view.getForeground());
- g.setFont(view.getFont());
- int ascent = g.getFontMetrics().getAscent();
- Utilities.drawTabbedText(txt, bounds.x, bounds.y + ascent, g, tabEx,
- txt.offset);
-
- if (view.isStrikeThrough())
- {
- int strikeHeight = (int) (getAscent(view) / 2);
- g.drawLine(bounds.x, bounds.y + strikeHeight, bounds.x + width,
- bounds.y + strikeHeight);
- }
- if (view.isUnderline())
+
+ // Find out the X location at which we have to paint.
+ int x = r.x;
+ int p = view.getStartOffset();
+ if (p != p0)
{
- int lineHeight = (int) getAscent(view);
- g.drawLine(bounds.x, bounds.y + lineHeight, bounds.x + width,
- bounds.y + lineHeight);
+ int width = Utilities.getTabbedTextWidth(txt, fontMetrics,x, tabEx,
+ p);
+ x += width;
}
- g.setColor(oldColor);
+ // Find out Y location.
+ int y = r.y + fontMetrics.getHeight() - fontMetrics.getDescent();
+
+ // Render the thing.
+ g.setFont(fontMetrics.getFont());
+ Utilities.drawTabbedText(txt, x, y, g, tabEx, p0);
+
}
/**
@@ -497,6 +480,16 @@ public class GlyphView extends View implements TabableView, Cloneable
private int length;
/**
+ * The x location against which the tab expansion is done.
+ */
+ private float tabX;
+
+ /**
+ * The tab expander that is used in this view.
+ */
+ private TabExpander tabExpander;
+
+ /**
* Creates a new <code>GlyphView</code> for the given <code>Element</code>.
*
* @param element the element that is rendered by this GlyphView
@@ -555,11 +548,29 @@ public class GlyphView extends View implements TabableView, Cloneable
int p0 = getStartOffset();
int p1 = getEndOffset();
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
Container c = getContainer();
- // Paint layered highlights if there are any.
+
+ Color fg = getForeground();
+ JTextComponent tc = null;
if (c instanceof JTextComponent)
{
- JTextComponent tc = (JTextComponent) c;
+ tc = (JTextComponent) c;
+ if (! tc.isEnabled())
+ fg = tc.getDisabledTextColor();
+ }
+ Color bg = getBackground();
+ if (bg != null)
+ {
+ g.setColor(bg);
+ System.err.println("fill background: " + bg);
+ g.fillRect(r.x, r.y, r.width, r.height);
+ }
+
+
+ // Paint layered highlights if there are any.
+ if (tc != null)
+ {
Highlighter h = tc.getHighlighter();
if (h instanceof LayeredHighlighter)
{
@@ -568,7 +579,45 @@ public class GlyphView extends View implements TabableView, Cloneable
}
}
- getGlyphPainter().paint(this, g, a, p0, p1);
+ g.setColor(fg);
+ glyphPainter.paint(this, g, a, p0, p1);
+ boolean underline = isUnderline();
+ boolean striked = isStrikeThrough();
+ if (underline || striked)
+ {
+ View parent = getParent();
+ // X coordinate.
+ if (parent != null && parent.getEndOffset() == p1)
+ {
+ // Strip whitespace.
+ Segment s = getText(p0, p1);
+ while (s.count > 0 && Character.isWhitespace(s.array[s.count - 1]))
+ {
+ p1--;
+ s.count--;
+ }
+ }
+ int x0 = r.x;
+ int p = getStartOffset();
+ TabExpander tabEx = getTabExpander();
+ if (p != p0)
+ x0 += (int) glyphPainter.getSpan(this, p, p0, tabEx, x0);
+ int x1 = x0 + (int) glyphPainter.getSpan(this, p0, p1, tabEx, x0);
+ // Y coordinate.
+ int y = r.y + r.height - (int) glyphPainter.getDescent(this);
+ if (underline)
+ {
+ int yTmp = y;
+ yTmp += 1;
+ g.drawLine(x0, yTmp, x1, yTmp);
+ }
+ if (striked)
+ {
+ int yTmp = y;
+ yTmp -= (int) glyphPainter.getAscent(this);
+ g.drawLine(x0, yTmp, x1, yTmp);
+ }
+ }
}
@@ -658,13 +707,7 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public TabExpander getTabExpander()
{
- TabExpander te = null;
- View parent = getParent();
-
- if (parent instanceof TabExpander)
- te = (TabExpander) parent;
-
- return te;
+ return tabExpander;
}
/**
@@ -678,8 +721,16 @@ public class GlyphView extends View implements TabableView, Cloneable
public float getTabbedSpan(float x, TabExpander te)
{
checkPainter();
+ TabExpander old = tabExpander;
+ tabExpander = te;
+ if (tabExpander != old)
+ {
+ // Changing the tab expander will lead to a relayout in the X_AXIS.
+ preferenceChanged(null, true, false);
+ }
+ tabX = x;
return getGlyphPainter().getSpan(this, getStartOffset(),
- getEndOffset(), te, x);
+ getEndOffset(), tabExpander, x);
}
/**
@@ -693,23 +744,8 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public float getPartialSpan(int p0, int p1)
{
- Element el = getElement();
- Document doc = el.getDocument();
- Segment seg = new Segment();
- try
- {
- doc.getText(p0, p1 - p0, seg);
- }
- catch (BadLocationException ex)
- {
- AssertionError ae;
- ae = new AssertionError("BadLocationException must not be thrown "
- + "here");
- ae.initCause(ex);
- throw ae;
- }
- FontMetrics fm = null; // Fetch font metrics somewhere.
- return Utilities.getTabbedTextWidth(seg, fm, 0, null, p0);
+ checkPainter();
+ return glyphPainter.getSpan(this, p0, p1, tabExpander, tabX);
}
/**
@@ -746,6 +782,8 @@ public class GlyphView extends View implements TabableView, Cloneable
return offs;
}
+ private Segment cached = new Segment();
+
/**
* Returns the text segment that this view is responsible for.
*
@@ -756,10 +794,9 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public Segment getText(int p0, int p1)
{
- Segment txt = new Segment();
try
{
- getDocument().getText(p0, p1 - p0, txt);
+ getDocument().getText(p0, p1 - p0, cached);
}
catch (BadLocationException ex)
{
@@ -770,7 +807,7 @@ public class GlyphView extends View implements TabableView, Cloneable
throw ae;
}
- return txt;
+ return cached;
}
/**
@@ -938,6 +975,8 @@ public class GlyphView extends View implements TabableView, Cloneable
if (p0 != getStartOffset() || end != getEndOffset())
{
brokenView = createFragment(p0, end);
+ if (brokenView instanceof GlyphView)
+ ((GlyphView) brokenView).tabX = pos;
}
}
return brokenView;
@@ -1007,7 +1046,7 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory vf)
{
- preferenceChanged(this, true, true);
+ preferenceChanged(null, true, true);
}
/**
@@ -1022,7 +1061,7 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public void insertUpdate(DocumentEvent e, Shape a, ViewFactory vf)
{
- preferenceChanged(this, true, false);
+ preferenceChanged(null, true, false);
}
/**
@@ -1037,7 +1076,7 @@ public class GlyphView extends View implements TabableView, Cloneable
*/
public void removeUpdate(DocumentEvent e, Shape a, ViewFactory vf)
{
- preferenceChanged(this, true, false);
+ preferenceChanged(null, true, false);
}
/**
diff --git a/javax/swing/text/StyleContext.java b/javax/swing/text/StyleContext.java
index b01d1060f..4dded0d04 100644
--- a/javax/swing/text/StyleContext.java
+++ b/javax/swing/text/StyleContext.java
@@ -48,10 +48,12 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Hashtable;
import java.util.Iterator;
+import java.util.Map;
import java.util.WeakHashMap;
import javax.swing.event.ChangeEvent;
@@ -467,7 +469,8 @@ public class StyleContext
/**
* A pool of immutable AttributeSets.
*/
- private transient WeakHashMap attributeSetPool = new WeakHashMap();
+ private transient Map attributeSetPool =
+ Collections.synchronizedMap(new WeakHashMap());
/**
* Creates a new instance of the style context. Add the default style
@@ -545,7 +548,7 @@ public class StyleContext
throws ClassNotFoundException, IOException
{
search = new SimpleAttributeSet();
- attributeSetPool = new WeakHashMap();
+ attributeSetPool = Collections.synchronizedMap(new WeakHashMap());
in.defaultReadObject();
}
@@ -650,7 +653,8 @@ public class StyleContext
return defaultStyleContext;
}
- public AttributeSet addAttribute(AttributeSet old, Object name, Object value)
+ public synchronized AttributeSet addAttribute(AttributeSet old, Object name,
+ Object value)
{
AttributeSet ret;
if (old.getAttributeCount() + 1 < getCompressionThreshold())
@@ -670,7 +674,8 @@ public class StyleContext
return ret;
}
- public AttributeSet addAttributes(AttributeSet old, AttributeSet attributes)
+ public synchronized AttributeSet addAttributes(AttributeSet old,
+ AttributeSet attributes)
{
AttributeSet ret;
if (old.getAttributeCount() + attributes.getAttributeCount()
@@ -701,7 +706,8 @@ public class StyleContext
cleanupPool();
}
- public AttributeSet removeAttribute(AttributeSet old, Object name)
+ public synchronized AttributeSet removeAttribute(AttributeSet old,
+ Object name)
{
AttributeSet ret;
if (old.getAttributeCount() - 1 <= getCompressionThreshold())
@@ -721,7 +727,8 @@ public class StyleContext
return ret;
}
- public AttributeSet removeAttributes(AttributeSet old, AttributeSet attributes)
+ public synchronized AttributeSet removeAttributes(AttributeSet old,
+ AttributeSet attributes)
{
AttributeSet ret;
if (old.getAttributeCount() <= getCompressionThreshold())
@@ -741,7 +748,8 @@ public class StyleContext
return ret;
}
- public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names)
+ public synchronized AttributeSet removeAttributes(AttributeSet old,
+ Enumeration<?> names)
{
AttributeSet ret;
if (old.getAttributeCount() <= getCompressionThreshold())
diff --git a/javax/swing/text/Utilities.java b/javax/swing/text/Utilities.java
index fa2d1ab52..d49d806cf 100644
--- a/javax/swing/text/Utilities.java
+++ b/javax/swing/text/Utilities.java
@@ -89,12 +89,12 @@ public class Utilities
// The font metrics of the current selected font.
FontMetrics metrics = g.getFontMetrics();
+
int ascent = metrics.getAscent();
// The current x and y pixel coordinates.
int pixelX = x;
- int pixelWidth = 0;
int pos = s.offset;
int len = 0;
@@ -103,39 +103,43 @@ public class Utilities
for (int offset = s.offset; offset < end; ++offset)
{
char c = buffer[offset];
- if (c == '\t')
+ switch (c)
{
+ case '\t':
if (len > 0) {
g.drawChars(buffer, pos, len, pixelX, y);
- pixelX += pixelWidth;
- pixelWidth = 0;
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ len = 0;
}
pos = offset+1;
- len = 0;
+ if (e != null)
+ pixelX = (int) e.nextTabStop((float) pixelX, startOffset + offset
+ - s.offset);
+ else
+ pixelX += metrics.charWidth(' ');
+ x = pixelX;
+ break;
+ case '\n':
+ case '\r':
+ if (len > 0) {
+ g.drawChars(buffer, pos, len, pixelX, y);
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ len = 0;
+ }
+ x = pixelX;
+ break;
+ default:
+ len += 1;
}
-
- switch (c)
- {
- case '\t':
- // In case we have a tab, we just 'jump' over the tab.
- // When we have no tab expander we just use the width of ' '.
- if (e != null)
- pixelX = (int) e.nextTabStop(pixelX,
- startOffset + offset - s.offset);
- else
- pixelX += metrics.charWidth(' ');
- break;
- default:
- ++len;
- pixelWidth += metrics.charWidth(buffer[offset]);
- break;
- }
}
if (len > 0)
- g.drawChars(buffer, pos, len, pixelX, y);
+ {
+ g.drawChars(buffer, pos, len, pixelX, y);
+ pixelX += metrics.charsWidth(buffer, pos, len);
+ }
- return pixelX + pixelWidth;
+ return pixelX;
}
/**
@@ -163,7 +167,9 @@ public class Utilities
// The current maximum width.
int maxWidth = 0;
- for (int offset = s.offset; offset < (s.offset + s.count); ++offset)
+ int end = s.offset + s.count;
+ int count = 0;
+ for (int offset = s.offset; offset < end; offset++)
{
switch (buffer[offset])
{
@@ -179,21 +185,18 @@ public class Utilities
case '\n':
// In case we have a newline, we must 'draw'
// the buffer and jump on the next line.
- pixelX += metrics.charWidth(buffer[offset]);
- maxWidth = Math.max(maxWidth, pixelX - x);
- pixelX = x;
- break;
- default:
- // Here we draw the char.
- pixelX += metrics.charWidth(buffer[offset]);
- break;
- }
+ pixelX += metrics.charsWidth(buffer, offset - count, count);
+ count = 0;
+ break;
+ default:
+ count++;
+ }
}
// Take the last line into account.
- maxWidth = Math.max(maxWidth, pixelX - x);
+ pixelX += metrics.charsWidth(buffer, end - count, count);
- return maxWidth;
+ return pixelX - x;
}
/**
@@ -228,43 +231,41 @@ public class Utilities
int x, TabExpander te, int p0,
boolean round)
{
- // At the end of the for loop, this holds the requested model location
- int pos;
+ int found = s.count;
int currentX = x0;
- int width = 0;
+ int nextX = currentX;
- for (pos = 0; pos < s.count; pos++)
+ int end = s.offset + s.count;
+ for (int pos = s.offset; pos < end && found == s.count; pos++)
{
- char nextChar = s.array[s.offset+pos];
-
- if (nextChar == 0)
- break;
+ char nextChar = s.array[pos];
if (nextChar != '\t')
- width = fm.charWidth(nextChar);
+ nextX += fm.charWidth(nextChar);
else
{
if (te == null)
- width = fm.charWidth(' ');
+ nextX += fm.charWidth(' ');
else
- width = ((int) te.nextTabStop(currentX, pos)) - currentX;
+ nextX += ((int) te.nextTabStop(nextX, p0 + pos - s.offset));
}
- if (round)
+ if (x >= currentX && x < nextX)
{
- if (currentX + (width>>1) > x)
- break;
- }
- else
- {
- if (currentX + width > x)
- break;
+ // Found position.
+ if ((! round) || ((x - currentX) < (nextX - x)))
+ {
+ found = pos - s.offset;
+ }
+ else
+ {
+ found = pos + 1 - s.offset;
+ }
}
-
- currentX += width;
+ currentX = nextX;
}
- return pos;
+ return found;
}
/**
diff --git a/javax/swing/text/html/BlockView.java b/javax/swing/text/html/BlockView.java
index d7519ef9a..2e781412c 100644
--- a/javax/swing/text/html/BlockView.java
+++ b/javax/swing/text/html/BlockView.java
@@ -171,6 +171,22 @@ public class BlockView extends BoxView
}
else
r = super.calculateMinorAxisRequirements(axis, r);
+
+ // Apply text alignment if appropriate.
+ if (axis == X_AXIS)
+ {
+ Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN);
+ if (o != null)
+ {
+ String al = o.toString().trim();
+ if (al.equals("center"))
+ r.alignment = 0.5f;
+ else if (al.equals("right"))
+ r.alignment = 1.0f;
+ else
+ r.alignment = 0.0f;
+ }
+ }
return r;
}
diff --git a/javax/swing/text/html/CSS.java b/javax/swing/text/html/CSS.java
index 6461dca9a..c82b6c537 100644
--- a/javax/swing/text/html/CSS.java
+++ b/javax/swing/text/html/CSS.java
@@ -415,6 +415,8 @@ public class CSS implements Serializable
new Attribute("border-left-color", false, null);
static final Attribute BORDER_RIGHT_COLOR =
new Attribute("border-right-color", false, null);
+ static final Attribute BORDER_SPACING =
+ new Attribute("border-spacing", false, null);
/**
* The attribute string.
@@ -516,7 +518,10 @@ public class CSS implements Serializable
else if (att == Attribute.MARGIN || att == Attribute.MARGIN_BOTTOM
|| att == Attribute.MARGIN_LEFT || att == Attribute.MARGIN_RIGHT
|| att == Attribute.MARGIN_TOP || att == Attribute.WIDTH
- || att == Attribute.HEIGHT)
+ || att == Attribute.HEIGHT
+ || att == Attribute.PADDING || att == Attribute.PADDING_BOTTOM
+ || att == Attribute.PADDING_LEFT || att == Attribute.PADDING_RIGHT
+ || att == Attribute.PADDING_TOP)
o = new Length(v);
else if (att == Attribute.BORDER_WIDTH || att == Attribute.BORDER_TOP_WIDTH
|| att == Attribute.BORDER_LEFT_WIDTH
@@ -543,7 +548,7 @@ public class CSS implements Serializable
String token = tokens.nextToken();
if (CSSColor.isValidColor(token))
atts.addAttribute(Attribute.BACKGROUND_COLOR,
- getValue(Attribute.BACKGROUND_COLOR, token));
+ new CSSColor(token));
}
}
}
diff --git a/javax/swing/text/html/HTMLDocument.java b/javax/swing/text/html/HTMLDocument.java
index 26e3fb4bc..ee59d7025 100644
--- a/javax/swing/text/html/HTMLDocument.java
+++ b/javax/swing/text/html/HTMLDocument.java
@@ -184,8 +184,6 @@ public class HTMLDocument extends DefaultStyledDocument
protected Element createLeafElement(Element parent, AttributeSet a, int p0,
int p1)
{
- RunElement el = new RunElement(parent, a, p0, p1);
- el.addAttribute(StyleConstants.NameAttribute, HTML.Tag.CONTENT);
return new RunElement(parent, a, p0, p1);
}
@@ -454,6 +452,8 @@ public class HTMLDocument extends DefaultStyledDocument
String name = null;
if (tag != null)
name = tag.toString();
+ if (name == null)
+ name = super.getName();
return name;
}
}
@@ -490,6 +490,8 @@ public class HTMLDocument extends DefaultStyledDocument
String name = null;
if (tag != null)
name = tag.toString();
+ if (name == null)
+ name = super.getName();
return name;
}
@@ -511,7 +513,17 @@ public class HTMLDocument extends DefaultStyledDocument
* @author Anthony Balkissoon abalkiss at redhat dot com
*/
public class HTMLReader extends HTMLEditorKit.ParserCallback
- {
+ {
+ /**
+ * The maximum token threshold. We don't grow it larger than this.
+ */
+ private static final int MAX_THRESHOLD = 10000;
+
+ /**
+ * The threshold growth factor.
+ */
+ private static final int GROW_THRESHOLD = 5;
+
/**
* Holds the current character attribute set *
*/
@@ -523,12 +535,6 @@ public class HTMLDocument extends DefaultStyledDocument
* A stack for character attribute sets *
*/
Stack charAttrStack = new Stack();
-
- /**
- * The parse stack. This stack holds HTML.Tag objects that reflect the
- * current position in the parsing process.
- */
- Stack parseStack = new Stack();
/** A mapping between HTML.Tag objects and the actions that handle them **/
HashMap tagToAction;
@@ -610,6 +616,11 @@ public class HTMLDocument extends DefaultStyledDocument
*/
Document textAreaDocument;
+ /**
+ * The token threshold. This gets increased while loading.
+ */
+ private int threshold;
+
public class TagAction
{
/**
@@ -816,7 +827,7 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void start(HTML.Tag t, MutableAttributeSet a)
{
- blockOpen(t, a);
+ super.start(t, a);
inParagraph = true;
}
@@ -826,7 +837,7 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void end(HTML.Tag t)
{
- blockClose(t);
+ super.end(t);
inParagraph = false;
}
}
@@ -1162,6 +1173,7 @@ public class HTMLDocument extends DefaultStyledDocument
this.offset = offset;
this.popDepth = popDepth;
this.pushDepth = pushDepth;
+ threshold = getTokenThreshold();
initTags();
}
@@ -1299,18 +1311,28 @@ public class HTMLDocument extends DefaultStyledDocument
*/
public void flush() throws BadLocationException
{
- DefaultStyledDocument.ElementSpec[] elements;
- elements = new DefaultStyledDocument.ElementSpec[parseBuffer.size()];
- parseBuffer.copyInto(elements);
- parseBuffer.removeAllElements();
- if (offset == 0)
- create(elements);
- else
- insert(offset, elements);
+ flushImpl();
+ }
- offset += HTMLDocument.this.getLength() - offset;
+ /**
+ * Flushes the buffer and handle partial inserts.
+ *
+ */
+ private void flushImpl()
+ throws BadLocationException
+ {
+ int oldLen = getLength();
+ int size = parseBuffer.size();
+ ElementSpec[] elems = new ElementSpec[size];
+ parseBuffer.copyInto(elems);
+ if (oldLen == 0)
+ create(elems);
+ else
+ insert(offset, elems);
+ parseBuffer.removeAllElements();
+ offset += getLength() - oldLen;
}
-
+
/**
* This method is called by the parser to indicate a block of
* text was encountered. Should insert the text appropriately.
@@ -1512,7 +1534,6 @@ public class HTMLDocument extends DefaultStyledDocument
DefaultStyledDocument.ElementSpec element;
- parseStack.push(t);
AbstractDocument.AttributeContext ctx = getAttributeContext();
AttributeSet copy = attr.copyAttributes();
copy = ctx.addAttribute(copy, StyleConstants.NameAttribute, t);
@@ -1542,25 +1563,17 @@ public class HTMLDocument extends DefaultStyledDocument
// If the previous tag is a start tag then we insert a synthetic
// content tag.
DefaultStyledDocument.ElementSpec prev;
- prev = (DefaultStyledDocument.ElementSpec)
- parseBuffer.get(parseBuffer.size() - 1);
- if (prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType)
+ prev = parseBuffer.size() > 0 ? (DefaultStyledDocument.ElementSpec)
+ parseBuffer.get(parseBuffer.size() - 1) : null;
+ if (prev != null &&
+ prev.getType() == DefaultStyledDocument.ElementSpec.StartTagType)
{
- AbstractDocument.AttributeContext ctx = getAttributeContext();
- AttributeSet attributes = ctx.getEmptySet();
- attributes = ctx.addAttribute(attributes, StyleConstants.NameAttribute,
- HTML.Tag.CONTENT);
- element = new DefaultStyledDocument.ElementSpec(attributes,
- DefaultStyledDocument.ElementSpec.ContentType,
- new char[0], 0, 0);
- parseBuffer.add(element);
+ addContent(new char[]{' '}, 0, 1);
}
element = new DefaultStyledDocument.ElementSpec(null,
DefaultStyledDocument.ElementSpec.EndTagType);
parseBuffer.addElement(element);
- if (parseStack.size() > 0)
- parseStack.pop();
}
/**
@@ -1615,11 +1628,13 @@ public class HTMLDocument extends DefaultStyledDocument
// Add the element to the buffer
parseBuffer.addElement(element);
- if (parseBuffer.size() > HTMLDocument.this.getTokenThreshold())
+ if (parseBuffer.size() > threshold)
{
+ if (threshold <= MAX_THRESHOLD)
+ threshold *= GROW_THRESHOLD;
try
{
- flush();
+ flushImpl();
}
catch (BadLocationException ble)
{
@@ -1734,10 +1749,6 @@ public class HTMLDocument extends DefaultStyledDocument
}
};
- // Set the parent HTML tag.
- reader.parseStack.push(parent.getAttributes().getAttribute(
- StyleConstants.NameAttribute));
-
return reader;
}
diff --git a/javax/swing/text/html/HTMLEditorKit.java b/javax/swing/text/html/HTMLEditorKit.java
index 85d5221d3..f3a3d90b6 100644
--- a/javax/swing/text/html/HTMLEditorKit.java
+++ b/javax/swing/text/html/HTMLEditorKit.java
@@ -39,8 +39,6 @@ exception statement from your version. */
package javax.swing.text.html;
-import gnu.classpath.NotImplementedException;
-
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
@@ -290,7 +288,7 @@ public class HTMLEditorKit
* Tag to check for in the document.
*/
protected HTML.Tag parentTag;
-
+
/**
* Initializes all fields.
*
@@ -394,20 +392,9 @@ public class HTMLEditorKit
Element insertElement,
String html, HTML.Tag parentTag,
HTML.Tag addTag)
- throws NotImplementedException
{
- /*
- As its name implies, this protected method is used when HTML is inserted at a
- boundary. (A boundary in this case is an offset in doc that exactly matches the
- beginning offset of the parentTag.) It performs the extra work required to keep
- the tag stack in shape and then calls insertHTML(). The editor and doc argu-
- ments are the editor pane and document where the HTML should go. The offset
- argument represents the cursor location or selection start in doc. The insert-
- Element and parentTag arguments are used to calculate the proper number of
- tag pops and pushes before inserting the HTML (via html and addTag, which are
- passed directly to insertHTML()).
- */
- // FIXME: not implemented
+ insertAtBoundry(editor, doc, offset, insertElement,
+ html, parentTag, addTag);
}
/**
@@ -433,8 +420,50 @@ public class HTMLEditorKit
String html, HTML.Tag parentTag,
HTML.Tag addTag)
{
- insertAtBoundary(editor, doc, offset, insertElement,
- html, parentTag, addTag);
+ Element parent = insertElement;
+ Element el;
+ // Find common parent element.
+ if (offset > 0 || insertElement == null)
+ {
+ el = doc.getDefaultRootElement();
+ while (el != null && el.getStartOffset() != offset
+ && ! el.isLeaf())
+ el = el.getElement(el.getElementIndex(offset));
+ parent = el != null ? el.getParentElement() : null;
+ }
+ if (parent != null)
+ {
+ int pops = 0;
+ int pushes = 0;
+ if (offset == 0 && insertElement != null)
+ {
+ el = parent;
+ while (el != null && ! el.isLeaf())
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pops++;
+ }
+ }
+ else
+ {
+ el = parent;
+ offset--;
+ while (el != null && ! el.isLeaf())
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pops++;
+ }
+ el = parent;
+ offset++;
+ while (el != null && el != insertElement)
+ {
+ el = el.getElement(el.getElementIndex(offset));
+ pushes++;
+ }
+ }
+ pops = Math.max(0, pops - 1);
+ insertHTML(editor, doc, offset, html, pops, pushes, addTag);
+ }
}
/**
@@ -444,16 +473,97 @@ public class HTMLEditorKit
*/
public void actionPerformed(ActionEvent ae)
{
- Object source = ae.getSource();
- if (source instanceof JEditorPane)
+ JEditorPane source = getEditor(ae);
+ if (source != null)
+ {
+ HTMLDocument d = getHTMLDocument(source);
+ int offset = source.getSelectionStart();
+ int length = d.getLength();
+ boolean inserted = true;
+ if (! tryInsert(source, d, offset, parentTag, addTag))
+ {
+ inserted = tryInsert(source, d, offset, alternateParentTag,
+ alternateAddTag);
+ }
+ if (inserted)
+ adjustSelection(source, d, offset, length);
+ }
+ }
+
+ /**
+ * Tries to insert the html chunk to the specified <code>addTag</code>.
+ *
+ * @param pane the editor
+ * @param doc the document
+ * @param offset the offset at which to insert
+ * @param tag the tag at which to insert
+ * @param addTag the add tag
+ *
+ * @return <code>true</code> when the html has been inserted successfully,
+ * <code>false</code> otherwise
+ */
+ private boolean tryInsert(JEditorPane pane, HTMLDocument doc, int offset,
+ HTML.Tag tag, HTML.Tag addTag)
+ {
+ boolean inserted = false;
+ Element el = findElementMatchingTag(doc, offset, tag);
+ if (el != null && el.getStartOffset() == offset)
+ {
+ insertAtBoundary(pane, doc, offset, el, html, tag, addTag);
+ inserted = true;
+ }
+ else if (offset > 0)
{
- JEditorPane pane = ((JEditorPane) source);
- Document d = pane.getDocument();
- if (d instanceof HTMLDocument)
- insertHTML(pane, (HTMLDocument) d, 0, html, 0, 0, addTag);
- // FIXME: is this correct parameters?
+ int depth = elementCountToTag(doc, offset - 1, tag);
+ if (depth != -1)
+ {
+ insertHTML(pane, doc, offset, html, depth, 0, addTag);
+ inserted = true;
+ }
+ }
+ return inserted;
+ }
+
+ /**
+ * Adjusts the selection after an insertion has been performed.
+ *
+ * @param pane the editor pane
+ * @param doc the document
+ * @param offset the insert offset
+ * @param oldLen the old document length
+ */
+ private void adjustSelection(JEditorPane pane, HTMLDocument doc,
+ int offset, int oldLen)
+ {
+ int newLen = doc.getLength();
+ if (newLen != oldLen && offset < newLen)
+ {
+ if (offset > 0)
+ {
+ String text;
+ try
+ {
+ text = doc.getText(offset - 1, 1);
+ }
+ catch (BadLocationException ex)
+ {
+ text = null;
+ }
+ if (text != null && text.length() > 0
+ && text.charAt(0) == '\n')
+ {
+ pane.select(offset, offset);
+ }
+ else
+ {
+ pane.select(offset + 1, offset + 1);
+ }
+ }
+ else
+ {
+ pane.select(1, 1);
+ }
}
- // FIXME: else not implemented
}
}
@@ -885,8 +995,36 @@ public class HTMLEditorKit
/**
* Actions for HTML
*/
- private static final Action[] defaultActions = {
- // FIXME: Add default actions for html
+ private static final Action[] defaultActions =
+ {
+ new InsertHTMLTextAction("InsertTable",
+ "<table border=1><tr><td></td></tr></table>",
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertTableRow",
+ "<table border=1><tr><td></td></tr></table>",
+ HTML.Tag.TABLE, HTML.Tag.TR,
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertTableCell",
+ "<table border=1><tr><td></td></tr></table>",
+ HTML.Tag.TR, HTML.Tag.TD,
+ HTML.Tag.BODY, HTML.Tag.TABLE),
+ new InsertHTMLTextAction("InsertUnorderedList",
+ "<ul><li></li></ul>",
+ HTML.Tag.BODY, HTML.Tag.UL),
+ new InsertHTMLTextAction("InsertUnorderedListItem",
+ "<ul><li></li></ul>",
+ HTML.Tag.UL, HTML.Tag.LI,
+ HTML.Tag.BODY, HTML.Tag.UL),
+ new InsertHTMLTextAction("InsertOrderedList",
+ "<ol><li></li></ol>",
+ HTML.Tag.BODY, HTML.Tag.OL),
+ new InsertHTMLTextAction("InsertOrderedListItem",
+ "<ol><li></li></ol>",
+ HTML.Tag.OL, HTML.Tag.LI,
+ HTML.Tag.BODY, HTML.Tag.OL),
+ new InsertHTMLTextAction("InsertPre",
+ "<pre></pre>", HTML.Tag.BODY, HTML.Tag.PRE)
+ // TODO: The reference impl has an InsertHRAction too.
};
/**
@@ -956,8 +1094,15 @@ public class HTMLEditorKit
*/
public Document createDefaultDocument()
{
- HTMLDocument document = new HTMLDocument(getStyleSheet());
+ // Protect the shared stylesheet.
+ StyleSheet styleSheet = getStyleSheet();
+ StyleSheet ss = new StyleSheet();
+ ss.addStyleSheet(styleSheet);
+
+ HTMLDocument document = new HTMLDocument(ss);
document.setParser(getParser());
+ document.setAsynchronousLoadPriority(4);
+ document.setTokenThreshold(100);
return document;
}
diff --git a/javax/swing/text/html/ImageView.java b/javax/swing/text/html/ImageView.java
index ff0d3ea40..f073c6d05 100644
--- a/javax/swing/text/html/ImageView.java
+++ b/javax/swing/text/html/ImageView.java
@@ -2,17 +2,19 @@ package javax.swing.text.html;
import gnu.javax.swing.text.html.CombinedAttributes;
import gnu.javax.swing.text.html.ImageViewIconFactory;
+import gnu.javax.swing.text.html.css.Length;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Rectangle;
import java.awt.Shape;
+import java.awt.Toolkit;
+import java.awt.image.ImageObserver;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.Icon;
-import javax.swing.ImageIcon;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
@@ -29,15 +31,39 @@ import javax.swing.text.html.HTML.Attribute;
public class ImageView extends View
{
/**
+ * Tracks image loading state and performs the necessary layout updates.
+ */
+ class Observer
+ implements ImageObserver
+ {
+
+ public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height)
+ {
+ boolean widthChanged = false;
+ if ((flags & ImageObserver.WIDTH) != 0
+ && ! getElement().getAttributes().isDefined(HTML.Attribute.WIDTH))
+ widthChanged = true;
+ boolean heightChanged = false;
+ if ((flags & ImageObserver.HEIGHT) != 0
+ && ! getElement().getAttributes().isDefined(HTML.Attribute.HEIGHT))
+ widthChanged = true;
+ if (widthChanged || heightChanged)
+ preferenceChanged(ImageView.this, widthChanged, heightChanged);
+ return (flags & ALLBITS) != 0;
+ }
+
+ }
+
+ /**
* True if the image loads synchronuosly (on demand). By default, the image
* loads asynchronuosly.
*/
boolean loadOnDemand;
-
+
/**
* The image icon, wrapping the image,
*/
- ImageIcon imageIcon;
+ Image image;
/**
* The image state.
@@ -45,6 +71,46 @@ public class ImageView extends View
byte imageState = MediaTracker.LOADING;
/**
+ * True when the image needs re-loading, false otherwise.
+ */
+ private boolean reloadImage;
+
+ /**
+ * True when the image properties need re-loading, false otherwise.
+ */
+ private boolean reloadProperties;
+
+ /**
+ * True when the width is set as CSS/HTML attribute.
+ */
+ private boolean haveWidth;
+
+ /**
+ * True when the height is set as CSS/HTML attribute.
+ */
+ private boolean haveHeight;
+
+ /**
+ * True when the image is currently loading.
+ */
+ private boolean loading;
+
+ /**
+ * The current width of the image.
+ */
+ private int width;
+
+ /**
+ * The current height of the image.
+ */
+ private int height;
+
+ /**
+ * Our ImageObserver for tracking the loading state.
+ */
+ private ImageObserver observer;
+
+ /**
* Creates the image view that represents the given element.
*
* @param element the element, represented by this image view.
@@ -52,25 +118,34 @@ public class ImageView extends View
public ImageView(Element element)
{
super(element);
+ observer = new Observer();
+ reloadProperties = true;
+ reloadImage = true;
}
/**
* Load or reload the image. This method initiates the image reloading. After
* the image is ready, the repaint event will be scheduled. The current image,
* if it already exists, will be discarded.
- *
- * @param itsTime
- * also load if the "on demand" property is set
*/
- void reloadImage(boolean itsTime)
+ private void reloadImage()
{
- URL url = getImageURL();
- if (url == null)
- imageState = (byte) MediaTracker.ERRORED;
- else if (!(loadOnDemand && !itsTime))
- imageIcon = new ImageIcon(url);
- else
- imageState = (byte) MediaTracker.LOADING;
+ loading = true;
+ reloadImage = false;
+ haveWidth = false;
+ haveHeight = false;
+ image = null;
+ width = 0;
+ height = 0;
+ try
+ {
+ loadImage();
+ updateSize();
+ }
+ finally
+ {
+ loading = false;
+ }
}
/**
@@ -159,10 +234,8 @@ public class ImageView extends View
*/
public Image getImage()
{
- if (imageIcon == null)
- return null;
- else
- return imageIcon.getImage();
+ updateState();
+ return image;
}
/**
@@ -245,9 +318,9 @@ public class ImageView extends View
if (axis == View.X_AXIS)
{
- Object w = attrs.getAttribute(Attribute.WIDTH);
- if (w != null)
- return Integer.parseInt(w.toString());
+ Object w = attrs.getAttribute(CSS.Attribute.WIDTH);
+ if (w instanceof Length)
+ return ((Length) w).getValue();
else if (image != null)
return image.getWidth(getContainer());
else
@@ -255,9 +328,9 @@ public class ImageView extends View
}
else if (axis == View.Y_AXIS)
{
- Object w = attrs.getAttribute(Attribute.HEIGHT);
- if (w != null)
- return Integer.parseInt(w.toString());
+ Object w = attrs.getAttribute(CSS.Attribute.HEIGHT);
+ if (w instanceof Length)
+ return ((Length) w).getValue();
else if (image != null)
return image.getHeight(getContainer());
else
@@ -291,7 +364,7 @@ public class ImageView extends View
{
return getAltText();
}
-
+
/**
* Paints the image or one of the two image state icons. The image is resized
* to the shape bounds. If there is no image available, the alternative text
@@ -305,83 +378,22 @@ public class ImageView extends View
*/
public void paint(Graphics g, Shape bounds)
{
- Rectangle r = bounds.getBounds();
-
- if (imageIcon == null)
-
- {
- // Loading image on demand, rendering the loading icon so far.
- reloadImage(true);
-
- // The reloadImage sets the imageIcon, unless the URL is broken
- // or malformed.
- if (imageIcon != null)
- {
- if (imageIcon.getImageLoadStatus() != MediaTracker.COMPLETE)
- {
- // Render "not ready" icon, unless the image is ready
- // immediately.
- renderIcon(g, r, getLoadingImageIcon());
- // Add the listener to repaint when the icon will be ready.
- imageIcon.setImageObserver(getContainer());
- return;
- }
- }
- else
- {
- renderIcon(g, r, getNoImageIcon());
- return;
- }
- }
-
- imageState = (byte) imageIcon.getImageLoadStatus();
-
- switch (imageState)
- {
- case MediaTracker.ABORTED:
- case MediaTracker.ERRORED:
- renderIcon(g, r, getNoImageIcon());
- break;
- case MediaTracker.LOADING:
- // If the image is not loaded completely, we still render it, as the
- // partial image may be available.
- case MediaTracker.COMPLETE:
+ updateState();
+ Rectangle r = bounds instanceof Rectangle ? (Rectangle) bounds
+ : bounds.getBounds();
+ Image image = getImage();
+ if (image != null)
{
- // Paint the scaled image.
- Image scaled = imageIcon.getImage().getScaledInstance(
- r.width,
- r.height,
- Image.SCALE_DEFAULT);
- ImageIcon painter = new ImageIcon(scaled);
- painter.paintIcon(getContainer(), g, r.x, r.y);
+ g.drawImage(image, r.x, r.y, r.width, r.height, observer);
}
- break;
- }
- }
-
- /**
- * Render "no image" icon and the alternative "no image" text. The text is
- * rendered right from the icon and is aligned to the icon bottom.
- */
- private void renderIcon(Graphics g, Rectangle bounds, Icon icon)
- {
- Shape current = g.getClip();
- try
+ else
{
- g.setClip(bounds);
+ Icon icon = getNoImageIcon();
if (icon != null)
- {
- icon.paintIcon(getContainer(), g, bounds.x, bounds.y);
- g.drawString(getAltText(), bounds.x + icon.getIconWidth(),
- bounds.y + icon.getIconHeight());
- }
- }
- finally
- {
- g.setClip(current);
+ icon.paintIcon(getContainer(), g, r.x, r.y);
}
}
-
+
/**
* Set if the image should be loaded only when needed (synchronuosly). By
* default, the image loads asynchronuosly. If the image is not yet ready, the
@@ -398,9 +410,7 @@ public class ImageView extends View
*/
protected void setPropertiesFromAttributes()
{
- // In the current implementation, nothing is cached yet, unless the image
- // itself.
- imageIcon = null;
+ // FIXME: Implement this properly.
}
/**
@@ -436,9 +446,90 @@ public class ImageView extends View
*/
public void setSize(float width, float height)
{
- if (imageIcon == null)
- reloadImage(false);
+ updateState();
+ // TODO: Implement this when we have an alt view for the alt=... attribute.
}
-
+ /**
+ * This makes sure that the image and properties have been loaded.
+ */
+ private void updateState()
+ {
+ if (reloadImage)
+ reloadImage();
+ if (reloadProperties)
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Actually loads the image.
+ */
+ private void loadImage()
+ {
+ URL src = getImageURL();
+ Image newImage = null;
+ if (src != null)
+ {
+ // Call getImage(URL) to allow the toolkit caching of that image URL.
+ newImage = Toolkit.getDefaultToolkit().getImage(src);
+ if (newImage != null && getLoadsSynchronously())
+ {
+ // Load image synchronously.
+ MediaTracker tracker = new MediaTracker(getContainer());
+ tracker.addImage(newImage, 0);
+ try
+ {
+ tracker.waitForID(0);
+ }
+ catch (InterruptedException ex)
+ {
+ Thread.interrupted();
+ }
+
+ }
+ }
+ image = newImage;
+ }
+
+ /**
+ * Updates the size parameters of the image.
+ */
+ private void updateSize()
+ {
+ int newW = 0;
+ int newH = 0;
+ Image newIm = getImage();
+ if (newIm != null)
+ {
+ AttributeSet atts = getAttributes();
+ // Fetch width.
+ Length l = (Length) atts.getAttribute(CSS.Attribute.WIDTH);
+ if (l != null)
+ {
+ newW = (int) l.getValue();
+ haveWidth = true;
+ }
+ else
+ {
+ newW = newIm.getWidth(observer);
+ }
+ // Fetch height.
+ l = (Length) atts.getAttribute(CSS.Attribute.HEIGHT);
+ if (l != null)
+ {
+ newH = (int) l.getValue();
+ haveHeight = true;
+ }
+ else
+ {
+ newW = newIm.getWidth(observer);
+ }
+ // Go and trigger loading.
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ if (haveWidth || haveHeight)
+ tk.prepareImage(newIm, width, height, observer);
+ else
+ tk.prepareImage(newIm, -1, -1, observer);
+ }
+ }
}
diff --git a/javax/swing/text/html/ParagraphView.java b/javax/swing/text/html/ParagraphView.java
index e3f2817be..8443515d3 100644
--- a/javax/swing/text/html/ParagraphView.java
+++ b/javax/swing/text/html/ParagraphView.java
@@ -187,28 +187,14 @@ public class ParagraphView
SizeRequirements r)
{
r = super.calculateMinorAxisRequirements(axis, r);
- if (setCSSSpan(r, axis))
+ if (! setCSSSpan(r, axis))
{
- // If we have set the span from CSS, then we need to adjust
- // the margins.
- SizeRequirements parent = super.calculateMinorAxisRequirements(axis,
- null);
int margin = axis == X_AXIS ? getLeftInset() + getRightInset()
: getTopInset() + getBottomInset();
r.minimum -= margin;
r.preferred -= margin;
r.maximum -= margin;
}
- else
- {
- float min = 0;
- int n = getLayoutViewCount();
- for (int i = 0; i < n; i++)
- min = Math.max(getLayoutView(i).getMinimumSpan(axis), min);
- r.minimum = (int) min;
- r.preferred = Math.max(r.preferred, r.minimum);
- r.maximum = Math.max(r.maximum, r.preferred);
- }
return r;
}
diff --git a/javax/swing/text/html/StyleSheet.java b/javax/swing/text/html/StyleSheet.java
index add22e01c..3322a390c 100644
--- a/javax/swing/text/html/StyleSheet.java
+++ b/javax/swing/text/html/StyleSheet.java
@@ -50,6 +50,8 @@ import gnu.javax.swing.text.html.css.Selector;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -107,9 +109,9 @@ public class StyleSheet extends StyleContext
implements CSSParserCallback
{
/**
- * The current style.
+ * The current styles.
*/
- private CSSStyle style;
+ private CSSStyle[] styles;
/**
* The precedence of the stylesheet to be parsed.
@@ -133,9 +135,11 @@ public class StyleSheet extends StyleContext
*
* @param sel the selector
*/
- public void startStatement(Selector sel)
+ public void startStatement(Selector[] sel)
{
- style = new CSSStyle(precedence, sel);
+ styles = new CSSStyle[sel.length];
+ for (int i = 0; i < sel.length; i++)
+ styles[i] = new CSSStyle(precedence, sel[i]);
}
/**
@@ -143,8 +147,9 @@ public class StyleSheet extends StyleContext
*/
public void endStatement()
{
- css.add(style);
- style = null;
+ for (int i = 0; i < styles.length; i++)
+ css.add(styles[i]);
+ styles = null;
}
/**
@@ -157,9 +162,13 @@ public class StyleSheet extends StyleContext
{
CSS.Attribute cssAtt = CSS.getAttribute(property);
Object val = CSS.getValue(cssAtt, value);
- CSS.addInternal(style, cssAtt, value);
- if (cssAtt != null)
- style.addAttribute(cssAtt, val);
+ for (int i = 0; i < styles.length; i++)
+ {
+ CSSStyle style = styles[i];
+ CSS.addInternal(style, cssAtt, value);
+ if (cssAtt != null)
+ style.addAttribute(cssAtt, val);
+ }
}
}
@@ -172,11 +181,11 @@ public class StyleSheet extends StyleContext
implements Style, Comparable
{
- static final int PREC_UA = 400000;
- static final int PREC_NORM = 300000;
+ static final int PREC_UA = 0;
+ static final int PREC_NORM = 100000;
static final int PREC_AUTHOR_NORMAL = 200000;
- static final int PREC_AUTHOR_IMPORTANT = 100000;
- static final int PREC_USER_IMPORTANT = 0;
+ static final int PREC_AUTHOR_IMPORTANT = 300000;
+ static final int PREC_USER_IMPORTANT = 400000;
/**
* The priority of this style when matching CSS selectors.
@@ -231,8 +240,10 @@ public class StyleSheet extends StyleContext
/** Base font size (int) */
int baseFontSize;
- /** The style sheets stored. */
- StyleSheet[] styleSheet;
+ /**
+ * The linked style sheets stored.
+ */
+ private ArrayList linked;
/**
* Maps element names (selectors) to AttributSet (the corresponding style
@@ -424,6 +435,21 @@ public class StyleSheet extends StyleContext
styles.add(style);
}
+ // Add styles from linked stylesheets.
+ if (linked != null)
+ {
+ for (int i = linked.size() - 1; i >= 0; i--)
+ {
+ StyleSheet ss = (StyleSheet) linked.get(i);
+ for (int j = ss.css.size() - 1; j >= 0; j--)
+ {
+ CSSStyle style = (CSSStyle) ss.css.get(j);
+ if (style.selector.matches(tags, classes, ids))
+ styles.add(style);
+ }
+ }
+ }
+
// Sort selectors.
Collections.sort(styles);
Style[] styleArray = new Style[styles.size()];
@@ -444,7 +470,6 @@ public class StyleSheet extends StyleContext
*/
public Style getRule(String selector)
{
- Selector sel = new Selector(selector);
CSSStyle best = null;
for (Iterator i = css.iterator(); i.hasNext();)
{
@@ -477,6 +502,9 @@ public class StyleSheet extends StyleContext
// Shouldn't happen. And if, then we
System.err.println("IOException while parsing stylesheet: " + ex.getMessage());
}
+ // Clean up resolved styles cache so that the new styles are recognized
+ // on next stylesheet request.
+ resolvedStyles.clear();
}
/**
@@ -546,11 +574,9 @@ public class StyleSheet extends StyleContext
*/
public void addStyleSheet(StyleSheet ss)
{
- if (styleSheet == null)
- styleSheet = new StyleSheet[] {ss};
- else
- System.arraycopy(new StyleSheet[] {ss}, 0, styleSheet,
- styleSheet.length, 1);
+ if (linked == null)
+ linked = new ArrayList();
+ linked.add(ss);
}
/**
@@ -560,31 +586,9 @@ public class StyleSheet extends StyleContext
*/
public void removeStyleSheet(StyleSheet ss)
{
- if (styleSheet.length == 1 && styleSheet[0].equals(ss))
- styleSheet = null;
- else
+ if (linked != null)
{
- for (int i = 0; i < styleSheet.length; i++)
- {
- StyleSheet curr = styleSheet[i];
- if (curr.equals(ss))
- {
- StyleSheet[] tmp = new StyleSheet[styleSheet.length - 1];
- if (i != 0 && i != (styleSheet.length - 1))
- {
- System.arraycopy(styleSheet, 0, tmp, 0, i);
- System.arraycopy(styleSheet, i + 1, tmp, i,
- styleSheet.length - i - 1);
- }
- else if (i == 0)
- System.arraycopy(styleSheet, 1, tmp, 0, styleSheet.length - 1);
- else
- System.arraycopy(styleSheet, 0, tmp, 0, styleSheet.length - 1);
-
- styleSheet = tmp;
- break;
- }
- }
+ linked.remove(ss);
}
}
@@ -595,7 +599,17 @@ public class StyleSheet extends StyleContext
*/
public StyleSheet[] getStyleSheets()
{
- return styleSheet;
+ StyleSheet[] linkedSS;
+ if (linked != null)
+ {
+ linkedSS = new StyleSheet[linked.size()];
+ linkedSS = (StyleSheet[]) linked.toArray(linkedSS);
+ }
+ else
+ {
+ linkedSS = null;
+ }
+ return linkedSS;
}
/**
@@ -697,18 +711,39 @@ public class StyleSheet extends StyleContext
o = htmlAttrSet.getAttribute(HTML.Attribute.WIDTH);
if (o != null)
cssAttr = addAttribute(cssAttr, CSS.Attribute.WIDTH,
- CSS.getValue(CSS.Attribute.WIDTH, o.toString()));
+ new Length(o.toString()));
// The HTML height attribute maps directly to CSS height.
o = htmlAttrSet.getAttribute(HTML.Attribute.HEIGHT);
if (o != null)
cssAttr = addAttribute(cssAttr, CSS.Attribute.HEIGHT,
- CSS.getValue(CSS.Attribute.HEIGHT, o.toString()));
+ new Length(o.toString()));
o = htmlAttrSet.getAttribute(HTML.Attribute.NOWRAP);
if (o != null)
cssAttr = addAttribute(cssAttr, CSS.Attribute.WHITE_SPACE, "nowrap");
+ // Map cellspacing attr of tables to CSS border-spacing.
+ o = htmlAttrSet.getAttribute(HTML.Attribute.CELLSPACING);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.BORDER_SPACING,
+ new Length(o.toString()));
+
+ // For table cells and headers, fetch the cellpadding value from the
+ // parent table and set it as CSS padding attribute.
+ HTML.Tag tag = (HTML.Tag)
+ htmlAttrSet.getAttribute(StyleConstants.NameAttribute);
+ if ((tag == HTML.Tag.TD || tag == HTML.Tag.TH)
+ && htmlAttrSet instanceof Element)
+ {
+ Element el = (Element) htmlAttrSet;
+ AttributeSet tableAttrs = el.getParentElement().getParentElement()
+ .getAttributes();
+ o = tableAttrs.getAttribute(HTML.Attribute.CELLPADDING);
+ if (o != null)
+ cssAttr = addAttribute(cssAttr, CSS.Attribute.PADDING,
+ new Length(o.toString()));
+ }
// TODO: Add more mappings.
return cssAttr;
}
@@ -824,10 +859,7 @@ public class StyleSheet extends StyleContext
*/
public Font getFont(AttributeSet a)
{
- FontSize size = (FontSize) a.getAttribute(CSS.Attribute.FONT_SIZE);
- int realSize = 12;
- if (size != null)
- realSize = size.getValue();
+ int realSize = getFontSize(a);
// Decrement size for subscript and superscript.
Object valign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
@@ -852,6 +884,41 @@ public class StyleSheet extends StyleContext
}
/**
+ * Resolves the fontsize for a given set of attributes.
+ *
+ * @param atts the attributes
+ *
+ * @return the resolved font size
+ */
+ private int getFontSize(AttributeSet atts)
+ {
+ int size = 12;
+ if (atts.isDefined(CSS.Attribute.FONT_SIZE))
+ {
+ FontSize fs = (FontSize) atts.getAttribute(CSS.Attribute.FONT_SIZE);
+ if (fs.isRelative())
+ {
+ int parSize = 12;
+ AttributeSet resolver = atts.getResolveParent();
+ if (resolver != null)
+ parSize = getFontSize(resolver);
+ size = fs.getValue(parSize);
+ }
+ else
+ {
+ size = fs.getValue();
+ }
+ }
+ else
+ {
+ AttributeSet resolver = atts.getResolveParent();
+ if (resolver != null)
+ size = getFontSize(resolver);
+ }
+ return size;
+ }
+
+ /**
* Takes a set of attributes and turns it into a foreground
* color specification. This is used to specify things like, brigher, more hue
* etc.
@@ -1036,6 +1103,11 @@ public class StyleSheet extends StyleContext
*/
private Border border;
+ private float leftPadding;
+ private float rightPadding;
+ private float topPadding;
+ private float bottomPadding;
+
/**
* The background color.
*/
@@ -1048,9 +1120,17 @@ public class StyleSheet extends StyleContext
*/
BoxPainter(AttributeSet as, StyleSheet ss)
{
- Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT);
+ // Fetch margins.
+ Length l = (Length) as.getAttribute(CSS.Attribute.MARGIN);
+ if (l != null)
+ {
+ topInset = bottomInset = leftInset = rightInset = l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.MARGIN_LEFT);
if (l != null)
leftInset = l.getValue();
+ else if (as.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.UL)
+ System.err.println("UL margin left value: " + l + " atts: " + as);
l = (Length) as.getAttribute(CSS.Attribute.MARGIN_RIGHT);
if (l != null)
rightInset = l.getValue();
@@ -1061,6 +1141,26 @@ public class StyleSheet extends StyleContext
if (l != null)
bottomInset = l.getValue();
+ // Fetch padding.
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING);
+ if (l != null)
+ {
+ leftPadding = rightPadding = topPadding = bottomPadding =
+ l.getValue();
+ }
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_LEFT);
+ if (l != null)
+ leftPadding = l.getValue();
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_RIGHT);
+ if (l != null)
+ rightPadding = l.getValue();
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_TOP);
+ if (l != null)
+ topPadding = l.getValue();
+ l = (Length) as.getAttribute(CSS.Attribute.PADDING_BOTTOM);
+ if (l != null)
+ bottomPadding = l.getValue();
+
// Determine border.
border = new CSSBorder(as);
@@ -1090,21 +1190,25 @@ public class StyleSheet extends StyleContext
inset = topInset;
if (border != null)
inset += border.getBorderInsets(null).top;
+ inset += topPadding;
break;
case View.BOTTOM:
inset = bottomInset;
if (border != null)
inset += border.getBorderInsets(null).bottom;
+ inset += bottomPadding;
break;
case View.LEFT:
inset = leftInset;
if (border != null)
inset += border.getBorderInsets(null).left;
+ inset += leftPadding;
break;
case View.RIGHT:
inset = rightInset;
if (border != null)
inset += border.getBorderInsets(null).right;
+ inset += rightPadding;
break;
default:
inset = 0.0F;
@@ -1176,6 +1280,11 @@ public class StyleSheet extends StyleContext
}
/**
+ * Cached rectangle re-used in the paint method below.
+ */
+ private final Rectangle tmpRect = new Rectangle();
+
+ /**
* Paints the CSS list decoration according to the attributes given.
*
* @param g - the graphics configuration
@@ -1200,7 +1309,35 @@ public class StyleSheet extends StyleContext
if (tag != null && tag == HTML.Tag.LI)
{
g.setColor(Color.BLACK);
- g.fillOval((int) x - 15, (int) (h / 2 - 3 + y), 6, 6);
+ int centerX = (int) (x - 12);
+ int centerY = -1;
+ // For paragraphs (almost all cases) center bullet vertically
+ // in the middle of the first line.
+ tmpRect.setBounds((int) x, (int) y, (int) w, (int) h);
+ if (itemView.getViewCount() > 0)
+ {
+ View v1 = itemView.getView(0);
+ if (v1 instanceof ParagraphView && v1.getViewCount() > 0)
+ {
+ Shape a1 = itemView.getChildAllocation(0, tmpRect);
+ Rectangle r1 = a1 instanceof Rectangle ? (Rectangle) a1
+ : a1.getBounds();
+ ParagraphView par = (ParagraphView) v1;
+ Shape a = par.getChildAllocation(0, r1);
+ if (a != null)
+ {
+ Rectangle r = a instanceof Rectangle ? (Rectangle) a
+ : a.getBounds();
+ centerY = (int) (r.height / 2 + r.y);
+ }
+ }
+ }
+ if (centerY == -1)
+ {
+ System.err.println("WARNING LI child is not a paragraph view " + itemView.getView(0) + ", " + itemView.getViewCount());
+ centerY =(int) (h / 2 + y);
+ }
+ g.fillOval(centerX - 3, centerY - 3, 6, 6);
}
}
}
diff --git a/javax/swing/text/html/TableView.java b/javax/swing/text/html/TableView.java
index 2bd11ffcf..971d54cb6 100644
--- a/javax/swing/text/html/TableView.java
+++ b/javax/swing/text/html/TableView.java
@@ -38,9 +38,12 @@ exception statement from your version. */
package javax.swing.text.html;
+import java.awt.Shape;
+
import gnu.javax.swing.text.html.css.Length;
import javax.swing.SizeRequirements;
+import javax.swing.event.DocumentEvent;
import javax.swing.text.AttributeSet;
import javax.swing.text.BoxView;
import javax.swing.text.Element;
@@ -75,6 +78,12 @@ class TableView
super(el, X_AXIS);
}
+ public void replace(int offset, int len, View[] views)
+ {
+ super.replace(offset, len, views);
+ gridValid = false;
+ }
+
/**
* Overridden to make rows not resizable along the Y axis.
*/
@@ -84,7 +93,27 @@ class TableView
if (axis == Y_AXIS)
span = super.getPreferredSpan(axis);
else
- span = super.getMaximumSpan(axis);
+ span = Integer.MAX_VALUE;
+ return span;
+ }
+
+ public float getMinimumSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = totalColumnRequirements.minimum;
+ else
+ span = super.getMinimumSpan(axis);
+ return span;
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ float span;
+ if (axis == X_AXIS)
+ span = totalColumnRequirements.preferred;
+ else
+ span = super.getPreferredSpan(axis);
return span;
}
@@ -97,9 +126,10 @@ class TableView
{
if (r == null)
r = new SizeRequirements();
- r.minimum = totalColumnRequirements.minimum;
- r.preferred = totalColumnRequirements.preferred;
- r.maximum = totalColumnRequirements.maximum;
+ int adjust = (columnRequirements.length + 1) * cellSpacing;
+ r.minimum = totalColumnRequirements.minimum + adjust;
+ r.preferred = totalColumnRequirements.preferred + adjust;
+ r.maximum = totalColumnRequirements.maximum + adjust;
r.alignment = 0.0F;
return r;
}
@@ -123,6 +153,8 @@ class TableView
for (int j = 0; j < cv.colSpan; j++, realColumn++)
{
spans[i] += columnSpans[realColumn];
+ if (j < cv.colSpan - 1)
+ spans[i] += cellSpacing;
}
}
}
@@ -151,6 +183,14 @@ class TableView
super(el, Y_AXIS);
}
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ r = super.calculateMajorAxisRequirements(axis, r);
+ r.maximum = Integer.MAX_VALUE;
+ return r;
+ }
+
/**
* Overridden to fetch the columnSpan attibute.
*/
@@ -183,8 +223,10 @@ class TableView
/**
* The column requirements.
+ *
+ * Package private to avoid accessor methods.
*/
- private SizeRequirements[] columnRequirements;
+ SizeRequirements[] columnRequirements;
/**
* The overall requirements across all columns.
@@ -215,7 +257,14 @@ class TableView
/**
* Indicates if the grid setup is ok.
*/
- private boolean gridValid;
+ boolean gridValid;
+
+ /**
+ * Additional space that is added _between_ table cells.
+ *
+ * This is package private to avoid accessor methods.
+ */
+ int cellSpacing;
/**
* Creates a new HTML table view for the specified element.
@@ -318,6 +367,11 @@ class TableView
r.minimum = width;
}
+ // Adjust requirements when we have cell spacing.
+ int adjust = (columnRequirements.length + 1) * cellSpacing;
+ r.minimum += adjust;
+ r.preferred += adjust;
+
// Apply the alignment.
Object o = atts.getAttribute(CSS.Attribute.TEXT_ALIGN);
r.alignment = 0.0F;
@@ -332,6 +386,8 @@ class TableView
r.alignment = 1.0F;
}
+ // Make it not resize in the horizontal direction.
+ r.maximum = r.preferred;
return r;
}
@@ -343,6 +399,16 @@ class TableView
int[] spans)
{
updateGrid();
+
+ // Mark all rows as invalid.
+ int n = getViewCount();
+ for (int i = 0; i < n; i++)
+ {
+ View row = getView(i);
+ if (row instanceof RowView)
+ ((RowView) row).layoutChanged(axis);
+ }
+
layoutColumns(targetSpan);
super.layoutMinorAxis(targetSpan, axis, offsets, spans);
}
@@ -361,8 +427,12 @@ class TableView
// all columns of all rows.
for (int r = 0; r < numRows; r++)
{
- RowView rowView = (RowView) getView(r);
- int numCols = rowView.getViewCount();
+ View rowView = getView(r);
+ int numCols;
+ if (rowView instanceof RowView)
+ numCols = ((RowView) rowView).getViewCount();
+ else
+ numCols = 0;
// We collect the normal (non-relative) column requirements in the
// total variable and the relative requirements in the relTotal
@@ -533,7 +603,9 @@ class TableView
}
// Try to adjust the spans so that we fill the targetSpan.
- long diff = targetSpan - sumPref;
+ // For adjustments we have to use the targetSpan minus the cumulated
+ // cell spacings.
+ long diff = targetSpan - (n + 1) * cellSpacing - sumPref;
float factor = 0.0F;
int[] diffs = null;
if (diff != 0)
@@ -570,7 +642,7 @@ class TableView
}
// Actually perform adjustments.
- int totalOffs = 0;
+ int totalOffs = cellSpacing;
for (int i = 0; i < n; i++)
{
columnOffsets[i] = totalOffs;
@@ -580,8 +652,8 @@ class TableView
columnSpans[i] += Math.round(adjust);
}
// Avoid overflow here.
- totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i],
- Integer.MAX_VALUE);
+ totalOffs = (int) Math.min((long) totalOffs + (long) columnSpans[i]
+ + (long) cellSpacing, Integer.MAX_VALUE);
}
}
@@ -597,15 +669,23 @@ class TableView
int numRows = getViewCount();
for (int r = 0; r < numRows; r++)
{
- RowView rowView = (RowView) getView(r);
- int numCols = rowView.getViewCount();
+ View rowView = getView(r);
+ int numCols;
+ if (rowView instanceof RowView)
+ numCols = ((RowView) rowView).getViewCount();
+ else
+ numCols = 0;
maxColumns = Math.max(numCols, maxColumns);
}
columnWidths = new Length[maxColumns];
for (int r = 0; r < numRows; r++)
{
- RowView rowView = (RowView) getView(r);
- int numCols = rowView.getViewCount();
+ View rowView = getView(r);
+ int numCols;
+ if (rowView instanceof RowView)
+ numCols = ((RowView) rowView).getViewCount();
+ else
+ numCols = 0;
int colIndex = 0;
for (int c = 0; c < numCols; c++)
{
@@ -644,4 +724,86 @@ class TableView
span = super.getMaximumSpan(axis);
return span;
}
+
+ /**
+ * Overridden to fetch the CSS attributes when view gets connected.
+ */
+ public void setParent(View parent)
+ {
+ super.setParent(parent);
+ if (parent != null)
+ setPropertiesFromAttributes();
+ }
+
+ /**
+ * Fetches CSS and HTML layout attributes.
+ */
+ private void setPropertiesFromAttributes()
+ {
+ // Fetch and parse cell spacing.
+ Object o = getAttributes().getAttribute(CSS.Attribute.BORDER_SPACING);
+ if (o != null && o instanceof Length)
+ {
+ Length l = (Length) o;
+ cellSpacing = (int) l.getValue();
+ }
+ }
+
+ /**
+ * Overridden to adjust for cellSpacing.
+ */
+ protected SizeRequirements calculateMajorAxisRequirements(int axis,
+ SizeRequirements r)
+ {
+ r = super.calculateMajorAxisRequirements(axis, r);
+ int adjust = (getViewCount() + 1) * cellSpacing;
+ r.minimum += adjust;
+ r.preferred += adjust;
+ r.maximum += adjust;
+ return r;
+ }
+
+ /**
+ * Overridden to adjust for cellSpacing.
+ */
+ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
+ int spans[])
+ {
+ int adjust = (getViewCount() + 1) * cellSpacing;
+ super.layoutMajorAxis(targetSpan - adjust, axis, offsets, spans);
+ for (int i = 0; i < offsets.length; i++)
+ {
+ offsets[i] += (i + 1) * cellSpacing;
+ }
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
+ */
+ public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.insertUpdate(e, a, this);
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
+ */
+ public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.removeUpdate(e, a, this);
+ }
+
+ /**
+ * Overridden to replace view factory with this one.
+ */
+ public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f)
+ {
+ super.changedUpdate(e, a, this);
+ }
+
+ public void replace(int offset, int len, View[] views)
+ {
+ super.replace(offset, len, views);
+ gridValid = false;
+ }
}
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 52c49186d..0e685902a 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -5,7 +5,6 @@ JAVA_DEPEND = java.dep
## this file and restart the make process again
sinclude $(JAVA_DEPEND)
-propertydirs := $(shell cd $(top_srcdir)/resource && $(FIND) gnu java javax org META-INF -type d ! -name CVS -print)
propertyfiles := $(shell cd $(top_srcdir)/resource && $(FIND) gnu java javax org -name \*\.properties -print)
cssfiles := $(shell cd $(top_srcdir) && $(FIND) gnu java javax org -name \*\.css -print)
metafiles := $(shell cd $(top_srcdir)/resource && $(FIND) META-INF -name CVS -prune -o -name \*\.in -prune -o -type f -print)
@@ -104,24 +103,25 @@ glibj.zip: classes compile-classes resources
endif # USE_PREBUILT_GLIBJ_ZIP
resources: copy-vmresources.sh
- if ! test -d gnu; then mkdir gnu; fi
- if ! test -d gnu/java; then mkdir gnu/java; fi
- if ! test -d gnu/java/locale; then mkdir gnu/java/locale; fi
- if ! test -d gnu/javax/swing/plaf/gtk/icons; then mkdir -p gnu/javax/swing/plaf/gtk/icons; fi
- @list='$(propertydirs)'; for p in $$list; do \
- if ! test -d $$p; then mkdir $$p; fi; \
- done
@list='$(propertyfiles)'; for p in $$list; do \
+ dirname=`dirname $$p`; \
+ if ! test -d "$$dirname"; then mkdir -p "$$dirname"; fi; \
cp $(top_srcdir)/resource/$$p $$p; \
done
@list='$(cssfiles)'; for p in $$list; do \
+ dirname=`dirname $$p`; \
+ if ! test -d "$$dirname"; then mkdir -p "$$dirname"; fi; \
cp $(top_srcdir)/$$p $$p; \
done
@list='$(metafiles)'; for p in $$list; do \
+ dirname=`dirname $$p`; \
+ if ! test -d "$$dirname"; then mkdir -p "$$dirname"; fi; \
cp $(top_srcdir)/resource/$$p $$p; \
done
@$(SHELL) ./copy-vmresources.sh
@list='$(iconfiles)'; for p in $$list; do \
+ dirname=`dirname $$p`; \
+ if ! test -d "$$dirname"; then mkdir -p "$$dirname"; fi; \
cp $(top_srcdir)/$$p $$p; \
done
touch resources
diff --git a/lib/gen-classlist.sh.in b/lib/gen-classlist.sh.in
index a0d3a075a..ba540cfcc 100755
--- a/lib/gen-classlist.sh.in
+++ b/lib/gen-classlist.sh.in
@@ -62,7 +62,9 @@ for dir in $vm_dirlist; do
done
# Only include generated files once.
-if test ! "${top_builddir}" -ef "@top_srcdir@"; then
+abs_top_builddir=`cd "${top_builddir}"; pwd`
+abs_top_srcdir=`cd "@top_srcdir@"; pwd`
+if test "$abs_top_builddir" != "$abs_top_srcdir"; then
echo "Adding generated files in builddir '${top_builddir}'."
# Currently the only generated files are in gnu.*.
(cd ${top_builddir}; @FIND@ gnu -follow -name '*.java' -print) |
@@ -102,7 +104,7 @@ rm vm.add
rm tmp.omit
new=
-if test -e ${top_builddir}/lib/classes.2; then
+if test -f ${top_builddir}/lib/classes.2; then
p=`diff ${top_builddir}/lib/classes.2 ${top_builddir}/lib/classes.1`
if test "$p" != ""; then
new="true"
diff --git a/native/jni/gtk-peer/gdkfont.h b/native/jni/gtk-peer/gdkfont.h
index cf2333015..5545bccaa 100644
--- a/native/jni/gtk-peer/gdkfont.h
+++ b/native/jni/gtk-peer/gdkfont.h
@@ -92,7 +92,10 @@ extern struct state_table *cp_gtk_native_text_layout_state_table;
#define FONT_METRICS_DESCENT 2
#define FONT_METRICS_MAX_DESCENT 3
#define FONT_METRICS_MAX_ADVANCE 4
-#define NUM_FONT_METRICS 5
+#define FONT_METRICS_HEIGHT 5
+#define FONT_METRICS_UNDERLINE_OFFSET 6
+#define FONT_METRICS_UNDERLINE_THICKNESS 7
+#define NUM_FONT_METRICS 8
#define TEXT_METRICS_X_BEARING 0
#define TEXT_METRICS_Y_BEARING 1
diff --git a/native/jni/gtk-peer/gnu_java_awt_peer_gtk_CairoGraphics2D.c b/native/jni/gtk-peer/gnu_java_awt_peer_gtk_CairoGraphics2D.c
index 11b0426c6..bd63ac366 100644
--- a/native/jni/gtk-peer/gnu_java_awt_peer_gtk_CairoGraphics2D.c
+++ b/native/jni/gtk-peer/gnu_java_awt_peer_gtk_CairoGraphics2D.c
@@ -148,7 +148,8 @@ Java_gnu_java_awt_peer_gtk_CairoGraphics2D_setGradient
JNIEXPORT void JNICALL
Java_gnu_java_awt_peer_gtk_CairoGraphics2D_setPaintPixels
(JNIEnv *env __attribute__((unused)), jobject obj __attribute__((unused)),
- jlong pointer, jintArray jarr, jint w, jint h, jint stride, jboolean repeat)
+ jlong pointer, jintArray jarr, jint w, jint h, jint stride, jboolean repeat,
+ jint x, jint y)
{
struct cairographics2d *gr = NULL;
jint *jpixels = NULL;
@@ -184,12 +185,12 @@ Java_gnu_java_awt_peer_gtk_CairoGraphics2D_setPaintPixels
gr->pattern = cairo_pattern_create_for_surface (gr->pattern_surface);
g_assert (gr->pattern != NULL);
+ cairo_set_source_surface(gr->cr, gr->pattern_surface, x, y);
+
if (repeat)
- cairo_pattern_set_extend (gr->pattern, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_extend(cairo_get_source(gr->cr), CAIRO_EXTEND_REPEAT);
else
- cairo_pattern_set_extend (gr->pattern, CAIRO_EXTEND_NONE);
-
- cairo_set_source (gr->cr, gr->pattern);
+ cairo_pattern_set_extend(cairo_get_source(gr->cr), CAIRO_EXTEND_NONE);
}
JNIEXPORT void JNICALL
diff --git a/native/jni/gtk-peer/gnu_java_awt_peer_gtk_GdkFontPeer.c b/native/jni/gtk-peer/gnu_java_awt_peer_gtk_GdkFontPeer.c
index ddaece1b2..0837ee13c 100644
--- a/native/jni/gtk-peer/gnu_java_awt_peer_gtk_GdkFontPeer.c
+++ b/native/jni/gtk-peer/gnu_java_awt_peer_gtk_GdkFontPeer.c
@@ -129,94 +129,48 @@ Java_gnu_java_awt_peer_gtk_GdkFontPeer_releasePeerGraphicsResource
JNIEXPORT void JNICALL
Java_gnu_java_awt_peer_gtk_GdkFontPeer_getFontMetrics
- (JNIEnv *env, jobject java_font, jdoubleArray java_metrics)
+(JNIEnv *env, jobject java_font, jdoubleArray java_metrics)
{
+ FT_Face face;
struct peerfont *pfont = NULL;
jdouble *native_metrics = NULL;
- PangoFontMetrics *pango_metrics = NULL;
- PangoLayout* layout = NULL;
- PangoRectangle ink_rect;
- PangoRectangle logical_rect;
- PangoLayoutIter* iter = NULL;
- int pango_ascent = 0;
- int pango_descent = 0;
- int pango_ink_ascent = 0;
- int pango_ink_descent = 0;
- int baseline = 0;
- int java_ascent = 0;
- int java_descent = 0;
+ short x_ppem;
+ short y_ppem;
+ short units_per_em;
+ double factorx;
+ double factory;
gdk_threads_enter();
pfont = (struct peerfont *) NSA_GET_FONT_PTR (env, java_font);
g_assert (pfont != NULL);
-
- pango_metrics
- = pango_context_get_metrics (pfont->ctx, pfont->desc,
- gtk_get_default_language ());
+ face = pango_fc_font_lock_face ((PangoFcFont *)pfont->font);
native_metrics
= (*env)->GetDoubleArrayElements (env, java_metrics, NULL);
g_assert (native_metrics != NULL);
- pango_ascent = PANGO_PIXELS (pango_font_metrics_get_ascent (pango_metrics));
- pango_descent = PANGO_PIXELS (pango_font_metrics_get_descent (pango_metrics));
-
- layout = pango_layout_new (pfont->ctx);
-
- /* Pango seems to produce ascent and descent values larger than
- those that Sun produces for the same-sized font. It turns out
- that an average of the "ink ascent" and "logical ascent" closely
- approximates Sun's ascent values. Likewise for descent values.
- This is expensive but we cache GdkFontMetrics so this should only
- run once per Font instance. */
- pango_layout_set_text (layout, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL"
- "MNOPQRSTUVWXYZ0123456789", -1);
- pango_layout_set_font_description (layout, pfont->desc);
-
- pango_layout_get_pixel_extents (layout, &ink_rect, &logical_rect);
-
- iter = pango_layout_get_iter (layout);
-
- baseline = PANGO_PIXELS (pango_layout_iter_get_baseline (iter));
-
- pango_ink_ascent = baseline - ink_rect.y;
- pango_ink_descent = ink_rect.y + ink_rect.height - baseline;
+ x_ppem = face->size->metrics.x_ppem;
+ y_ppem = face->size->metrics.y_ppem;
+ units_per_em = face->units_per_EM;
+ factorx = units_per_em / x_ppem;
+ factory = units_per_em / y_ppem;
+ native_metrics[FONT_METRICS_ASCENT] = face->ascender / factory;
+ native_metrics[FONT_METRICS_MAX_ASCENT] = face->bbox.yMax / factory;
+ native_metrics[FONT_METRICS_DESCENT] = - face->descender / factory;
+ native_metrics[FONT_METRICS_MAX_DESCENT] = - face->bbox.yMin / factory;
+ native_metrics[FONT_METRICS_MAX_ADVANCE] = face->max_advance_width / factorx;
+ native_metrics[FONT_METRICS_HEIGHT] = face->height / factory;
+ native_metrics[FONT_METRICS_UNDERLINE_OFFSET] =
+ face->underline_position / factory;
+ native_metrics[FONT_METRICS_UNDERLINE_THICKNESS] =
+ face->underline_thickness / factory;
- java_ascent = (pango_ascent + pango_ink_ascent) >> 1;
- java_descent = (pango_descent + pango_ink_descent) >> 1;
-
- java_ascent = MAX(0, java_ascent);
- java_descent = MAX(0, java_descent);
-
- pango_ascent = MAX(0, pango_ascent);
- pango_descent = MAX(0, pango_descent);
-
- /* Pango monospaced fonts have smaller ascent metrics than Sun's so
- we return the logical ascent for monospaced fonts. */
- if (!strcmp (pango_font_description_get_family (pfont->desc),
- "Courier"))
- native_metrics[FONT_METRICS_ASCENT] = pango_ascent;
- else
- native_metrics[FONT_METRICS_ASCENT] = java_ascent;
-
- native_metrics[FONT_METRICS_MAX_ASCENT] = pango_ascent;
-
- native_metrics[FONT_METRICS_DESCENT] = java_descent;
-
- native_metrics[FONT_METRICS_MAX_DESCENT] = pango_descent;
-
- native_metrics[FONT_METRICS_MAX_ADVANCE]
- = PANGO_PIXELS (pango_font_metrics_get_approximate_char_width
- (pango_metrics));
-
(*env)->ReleaseDoubleArrayElements (env,
java_metrics,
native_metrics, 0);
- pango_font_metrics_unref (pango_metrics);
-
gdk_threads_leave();
}
diff --git a/native/jni/java-lang/java_lang_VMDouble.c b/native/jni/java-lang/java_lang_VMDouble.c
index f8c072192..2ee1f3146 100644
--- a/native/jni/java-lang/java_lang_VMDouble.c
+++ b/native/jni/java-lang/java_lang_VMDouble.c
@@ -37,6 +37,7 @@ obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
+#include <assert.h>
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
@@ -167,43 +168,133 @@ Java_java_lang_VMDouble_longBitsToDouble
return val.d;
}
-/*
- * Class: java_lang_VMDouble
- * Method: toString
- * Signature: (DZ)Ljava/lang/String;
+/**
+ * Parse a double from a char array.
*/
-JNIEXPORT jstring JNICALL
-Java_java_lang_VMDouble_toString
- (JNIEnv * env, jclass cls __attribute__ ((__unused__)), jdouble value, jboolean isFloat)
+static jdouble
+parseDoubleFromChars(JNIEnv * env, const char * buf)
{
- char buffer[50], result[50];
- int decpt, sign;
- char *s, *d;
- int i;
+ char *endptr;
+ jdouble val = 0.0;
+ const char *p = buf, *end, *last_non_ws, *temp;
+ int ok = 1;
#ifdef DEBUG
- fprintf (stderr, "java.lang.VMDouble.toString (%g)\n", value);
+ fprintf (stderr, "java.lang.VMDouble.parseDouble (%s)\n", buf);
#endif
- if ((*env)->CallStaticBooleanMethod (env, clsDouble, isNaNID, value))
- return (*env)->NewStringUTF (env, "NaN");
+ /* Trim the buffer, similar to String.trim(). First the leading
+ characters. */
+ while (*p && *p <= ' ')
+ ++p;
- if (value == POSITIVE_INFINITY)
- return (*env)->NewStringUTF (env, "Infinity");
+ /* Find the last non-whitespace character. This method is safe
+ even with multi-byte UTF-8 characters. */
+ end = p;
+ last_non_ws = NULL;
+ while (*end)
+ {
+ if (*end > ' ')
+ last_non_ws = end;
+ ++end;
+ }
- if (value == NEGATIVE_INFINITY)
- return (*env)->NewStringUTF (env, "-Infinity");
+ if (last_non_ws == NULL)
+ last_non_ws = p + strlen (p);
+ else
+ {
+ /* Skip past the last non-whitespace character. */
+ ++last_non_ws;
+ }
+
+ /* Check for infinity and NaN */
+ temp = p;
+ if (temp[0] == '+' || temp[0] == '-')
+ temp++;
+ if (strncmp ("Infinity", temp, (size_t) 8) == 0)
+ {
+ if (p[0] == '-')
+ return NEGATIVE_INFINITY;
+ return POSITIVE_INFINITY;
+ }
+ if (strncmp ("NaN", temp, (size_t) 3) == 0)
+ return NaN;
+
+ /* Skip a trailing `f' or `d'. */
+ if (last_non_ws > p
+ && (last_non_ws[-1] == 'f'
+ || last_non_ws[-1] == 'F'
+ || last_non_ws[-1] == 'd' || last_non_ws[-1] == 'D'))
+ --last_non_ws;
+
+ if (last_non_ws > p)
+ {
+ struct _Jv_reent reent;
+ memset (&reent, 0, sizeof reent);
- _dtoa (value, 0, 20, &decpt, &sign, NULL, buffer, (int) isFloat);
+ val = _strtod_r (&reent, p, &endptr);
+
+#ifdef DEBUG
+ fprintf (stderr, "java.lang.VMDouble.parseDouble val = %g\n", val);
+ fprintf (stderr, "java.lang.VMDouble.parseDouble %i != %i ???\n",
+ endptr, last_non_ws);
+#endif
+ if (endptr != last_non_ws)
+ ok = 0;
+ }
+ else
+ ok = 0;
+
+ if (!ok)
+ {
+ val = 0.0;
+ JCL_ThrowException (env,
+ "java/lang/NumberFormatException",
+ "unable to parse double");
+ }
+
+ return val;
+}
+
+#define MAXIMAL_DECIMAL_STRING_LENGTH 64
+
+/**
+ * Use _dtoa to print a double or a float as a string with the given precision.
+ */
+static void
+dtoa_toString
+(char * buffer, jdouble value, jint precision, jboolean isFloat)
+{
+ const int DTOA_MODE = 2;
+ char result[MAXIMAL_DECIMAL_STRING_LENGTH];
+ int decpt, sign;
+ char *s, *d;
+ int i;
+
+ /* use mode 2 to get at the digit stream, all other modes are useless
+ *
+ * since mode 2 only gives us as many digits as we need in precision, we need to
+ * add the digits in front of the floating point to it, if there is more than one
+ * to be printed. That's the case if the value is going to be printed using the
+ * normal notation, i.e. if it is 0 or >= 1.0e-3 and < 1.0e7.
+ */
+ int digits_in_front_of_floating_point = ceil(log10(value));
+
+ if (digits_in_front_of_floating_point > 1 && digits_in_front_of_floating_point < 7)
+ precision += digits_in_front_of_floating_point;
+
+ _dtoa (value, DTOA_MODE, precision, &decpt, &sign, NULL, buffer, (int) isFloat);
value = fabs (value);
s = buffer;
d = result;
+ /* Handle negative sign */
if (sign)
*d++ = '-';
+ /* Handle normal represenation */
if ((value >= 1e-3 && value < 1e7) || (value == 0))
{
if (decpt <= 0)
@@ -233,46 +324,111 @@ Java_java_lang_VMDouble_toString
*d = 0;
- return (*env)->NewStringUTF (env, result);
}
+ /* Handle scientific representaiton */
+ else
+ {
+ *d++ = *s++;
+ decpt--;
+ *d++ = '.';
- *d++ = *s++;
- decpt--;
- *d++ = '.';
+ if (*s == 0)
+ *d++ = '0';
- if (*s == 0)
- *d++ = '0';
+ while (*s)
+ *d++ = *s++;
- while (*s)
- *d++ = *s++;
+ *d++ = 'E';
- *d++ = 'E';
+ if (decpt < 0)
+ {
+ *d++ = '-';
+ decpt = -decpt;
+ }
- if (decpt < 0)
- {
- *d++ = '-';
- decpt = -decpt;
+ {
+ char exp[4];
+ char *e = exp + sizeof exp;
+
+ *--e = 0;
+ do
+ {
+ *--e = '0' + decpt % 10;
+ decpt /= 10;
+ }
+ while (decpt > 0);
+
+ while (*e)
+ *d++ = *e++;
+ }
+
+ *d = 0;
}
- {
- char exp[4];
- char *e = exp + sizeof exp;
+ /* copy the result into the buffer */
+ memcpy(buffer, result, MAXIMAL_DECIMAL_STRING_LENGTH);
+}
- *--e = 0;
- do
- {
- *--e = '0' + decpt % 10;
- decpt /= 10;
- }
- while (decpt > 0);
+/*
+ * Class: java_lang_VMDouble
+ * Method: toString
+ * Signature: (DZ)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL
+Java_java_lang_VMDouble_toString
+ (JNIEnv * env, jclass cls __attribute__ ((__unused__)), jdouble value, jboolean isFloat)
+{
+ char buf[MAXIMAL_DECIMAL_STRING_LENGTH];
+ const jint MAXIMAL_FLOAT_PRECISION = 10;
+ const jint MAXIMAL_DOUBLE_PRECISION = 19;
- while (*e)
- *d++ = *e++;
- }
+ jint maximal_precision;
+ jint least_necessary_precision = 2;
+ jboolean parsed_value_unequal;
+
+ if ((*env)->CallStaticBooleanMethod (env, clsDouble, isNaNID, value))
+ return (*env)->NewStringUTF (env, "NaN");
+
+ if (value == POSITIVE_INFINITY)
+ return (*env)->NewStringUTF (env, "Infinity");
+
+ if (value == NEGATIVE_INFINITY)
+ return (*env)->NewStringUTF (env, "-Infinity");
+
+ if (isFloat)
+ maximal_precision = MAXIMAL_FLOAT_PRECISION;
+ else
+ maximal_precision = MAXIMAL_DOUBLE_PRECISION;
+
+ /* Try to find the 'good enough' precision,
+ * that results in enough digits being printed to be able to
+ * convert the number back into the original double, but no
+ * further digits.
+ */
+
+ do {
+ jdouble parsed_value;
+
+ assert(least_necessary_precision <= maximal_precision);
+
+ /* Convert the value to a string and back. */
+ dtoa_toString(buf, value, least_necessary_precision, isFloat);
+
+ parsed_value = parseDoubleFromChars(env, buf);
- *d = 0;
+ /* Check whether the original value, and the value after conversion match. */
+ /* We need to cast floats to float to make sure that our ineqality check works
+ * well for floats as well as for doubles.
+ */
+ parsed_value_unequal = ( isFloat ?
+ (float) parsed_value != (float) value :
+ parsed_value != value);
- return (*env)->NewStringUTF (env, result);
+ least_necessary_precision++;
+ }
+ while (parsed_value_unequal);
+
+ return (*env)->NewStringUTF (env, buf);
}
/*
@@ -286,7 +442,6 @@ Java_java_lang_VMDouble_parseDouble
{
jboolean isCopy;
const char *buf;
- char *endptr;
jdouble val = 0.0;
if (str == NULL)
@@ -302,83 +457,7 @@ Java_java_lang_VMDouble_parseDouble
}
else
{
- const char *p = buf, *end, *last_non_ws, *temp;
- int ok = 1;
-
-#ifdef DEBUG
- fprintf (stderr, "java.lang.VMDouble.parseDouble (%s)\n", buf);
-#endif
-
- /* Trim the buffer, similar to String.trim(). First the leading
- characters. */
- while (*p && *p <= ' ')
- ++p;
-
- /* Find the last non-whitespace character. This method is safe
- even with multi-byte UTF-8 characters. */
- end = p;
- last_non_ws = NULL;
- while (*end)
- {
- if (*end > ' ')
- last_non_ws = end;
- ++end;
- }
-
- if (last_non_ws == NULL)
- last_non_ws = p + strlen (p);
- else
- {
- /* Skip past the last non-whitespace character. */
- ++last_non_ws;
- }
-
- /* Check for infinity and NaN */
- temp = p;
- if (temp[0] == '+' || temp[0] == '-')
- temp++;
- if (strncmp ("Infinity", temp, (size_t) 8) == 0)
- {
- if (p[0] == '-')
- return NEGATIVE_INFINITY;
- return POSITIVE_INFINITY;
- }
- if (strncmp ("NaN", temp, (size_t) 3) == 0)
- return NaN;
-
- /* Skip a trailing `f' or `d'. */
- if (last_non_ws > p
- && (last_non_ws[-1] == 'f'
- || last_non_ws[-1] == 'F'
- || last_non_ws[-1] == 'd' || last_non_ws[-1] == 'D'))
- --last_non_ws;
-
- if (last_non_ws > p)
- {
- struct _Jv_reent reent;
- memset (&reent, 0, sizeof reent);
-
- val = _strtod_r (&reent, p, &endptr);
-
-#ifdef DEBUG
- fprintf (stderr, "java.lang.VMDouble.parseDouble val = %g\n", val);
- fprintf (stderr, "java.lang.VMDouble.parseDouble %i != %i ???\n",
- endptr, last_non_ws);
-#endif
- if (endptr != last_non_ws)
- ok = 0;
- }
- else
- ok = 0;
-
- if (!ok)
- {
- val = 0.0;
- JCL_ThrowException (env,
- "java/lang/NumberFormatException",
- "unable to parse double");
- }
-
+ val = parseDoubleFromChars(env, buf);
(*env)->ReleaseStringUTFChars (env, str, buf);
}
diff --git a/native/jni/java-net/gnu_java_net_VMPlainSocketImpl.c b/native/jni/java-net/gnu_java_net_VMPlainSocketImpl.c
index 53ef04d47..cdfbe7397 100644
--- a/native/jni/java-net/gnu_java_net_VMPlainSocketImpl.c
+++ b/native/jni/java-net/gnu_java_net_VMPlainSocketImpl.c
@@ -46,7 +46,9 @@ exception statement from your version. */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
+#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
+#endif
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <net/if.h>
@@ -877,7 +879,7 @@ Java_gnu_java_net_VMPlainSocketImpl_leaveGroup6 (JNIEnv *env,
static uint32_t
getif_address (JNIEnv *env, const char *ifname)
{
-#ifdef HAVE_GETIFADDRS
+#if defined (HAVE_IFADDRS_H) && defined (HAVE_GETIFADDRS)
struct ifaddrs *ifaddrs, *i;
uint32_t addr = 0;
int foundaddr = 0;
@@ -913,13 +915,13 @@ getif_address (JNIEnv *env, const char *ifname)
JCL_ThrowException (env, "java/lang/InternalError",
"getifaddrs not available");
return 0;
-#endif /* HAVE_GETIFADDRS */
+#endif /* HAVE_IFADDRS_H && HAVE_GETIFADDRS */
}
static int
getif_index (JNIEnv *env, const char *ifname)
{
-#ifdef HAVE_GETIFADDRS
+#if defined (HAVE_IFADDRS_H) && defined (HAVE_GETIFADDRS)
struct ifaddrs *ifaddrs, *i;
char *lastname = NULL;
int index = 1;
diff --git a/native/jni/java-net/java_net_VMNetworkInterface.c b/native/jni/java-net/java_net_VMNetworkInterface.c
index 203f6acd0..b51bf68ec 100644
--- a/native/jni/java-net/java_net_VMNetworkInterface.c
+++ b/native/jni/java-net/java_net_VMNetworkInterface.c
@@ -41,7 +41,9 @@ exception statement from your version. */
#include <sys/types.h>
#include <sys/socket.h>
+#ifdef HAVE_IFADDRS_H
#include <ifaddrs.h>
+#endif
#include <netinet/in.h>
#include <errno.h>
#include <stdlib.h>
@@ -111,7 +113,7 @@ free_netif_list (JNIEnv *env, struct netif_entry *list)
JNIEXPORT jobjectArray JNICALL
Java_java_net_VMNetworkInterface_getVMInterfaces (JNIEnv * env, jclass clazz)
{
-#ifdef HAVE_GETIFADDRS
+#if defined (HAVE_IFADDRS_H) && defined (HAVE_GETIFADDRS)
struct ifaddrs *ifaddrs, *i;
struct netif_entry *iflist = NULL, *e;
jobjectArray netifs;
diff --git a/native/jni/java-nio/gnu_java_nio_VMSelector.c b/native/jni/java-nio/gnu_java_nio_VMSelector.c
index f8a40aa7a..74a408c75 100644
--- a/native/jni/java-nio/gnu_java_nio_VMSelector.c
+++ b/native/jni/java-nio/gnu_java_nio_VMSelector.c
@@ -219,7 +219,7 @@ Java_gnu_java_nio_VMSelector_select (JNIEnv * env,
fd_set except_fds;
struct timeval real_time_data;
struct timeval *time_data = NULL;
- char message_buf[BUF_SIZE + 1];
+ char *message;
/* If a legal timeout value isn't given, use NULL.
* This means an infinite timeout. The specification
@@ -270,7 +270,8 @@ Java_gnu_java_nio_VMSelector_select (JNIEnv * env,
if (result < 0)
{
-
+#if defined(HAVE_STRERROR_R)
+ char message_buf[BUF_SIZE+1];
int errorcode = -result;
if (strerror_r (errorcode, message_buf, BUF_SIZE))
@@ -283,7 +284,12 @@ Java_gnu_java_nio_VMSelector_select (JNIEnv * env,
return 0;
}
- JCL_ThrowException (env, "java/io/IOException", message_buf);
+ message = message_buf;
+#else
+ message = strerror(errno);
+#endif
+
+ JCL_ThrowException (env, "java/io/IOException", message);
return 0;
}
diff --git a/native/jni/midi-dssi/Makefile.am b/native/jni/midi-dssi/Makefile.am
index 48c8051cc..692579d46 100644
--- a/native/jni/midi-dssi/Makefile.am
+++ b/native/jni/midi-dssi/Makefile.am
@@ -9,4 +9,7 @@ libgjsmdssi_la_LDFLAGS = -avoid-version
AM_LDFLAGS = @CLASSPATH_MODULE@
AM_CPPFLAGS = @CLASSPATH_INCLUDES@
-AM_CFLAGS = @WARNING_CFLAGS@ @STRICT_WARNING_CFLAGS@ @ERROR_CFLAGS@
+# No STRICT_WARNING_CFLAGS here as we use dlsym to load the address of
+# a function,and ISO C prohibits casting void pointers, like those returned
+# by dlsym, to function pointers.
+AM_CFLAGS = @WARNING_CFLAGS@ @ERROR_CFLAGS@
diff --git a/native/target/.cvsignore b/native/target/.cvsignore
deleted file mode 100644
index e9f2658a6..000000000
--- a/native/target/.cvsignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*.o
-*.a
-*.lo
-*.la
-.libs
-.deps
-Makefile
-Makefile.in
diff --git a/native/target/Linux/.cvsignore b/native/target/Linux/.cvsignore
deleted file mode 100644
index e9f2658a6..000000000
--- a/native/target/Linux/.cvsignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*.o
-*.a
-*.lo
-*.la
-.libs
-.deps
-Makefile
-Makefile.in
diff --git a/native/target/generic/.cvsignore b/native/target/generic/.cvsignore
deleted file mode 100644
index e9f2658a6..000000000
--- a/native/target/generic/.cvsignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*.o
-*.a
-*.lo
-*.la
-.libs
-.deps
-Makefile
-Makefile.in
diff --git a/tools/gnu/classpath/tools/appletviewer/TagParser.java b/tools/gnu/classpath/tools/appletviewer/TagParser.java
index 68dce97e0..b5bdca70f 100644
--- a/tools/gnu/classpath/tools/appletviewer/TagParser.java
+++ b/tools/gnu/classpath/tools/appletviewer/TagParser.java
@@ -230,12 +230,66 @@ public class TagParser
t.archives = parseArchives(val, t);
val = t.archives.toString();
}
-
+ val = unescapeString(val);
t.parameters.put(key.toLowerCase(), val);
}
}
/**
+ * This method does the same thing as the g_strcompress function in glib.
+ *
+ * @param value
+ * @return value in its original one-byte equivalence.
+ */
+ private static String unescapeString(String value)
+ {
+ String unescVal = "";
+ for (int i = 0; i < value.length(); i++)
+ {
+ if (i == value.length() - 1)
+ {
+ unescVal = unescVal.concat(value.substring(i));
+ break;
+ }
+ if (value.charAt(i) == '\\')
+ {
+ switch (value.charAt(i + 1))
+ {
+ case 'b':
+ unescVal = unescVal.concat("\b");
+ break;
+ case 'f':
+ unescVal = unescVal.concat("\f");
+ break;
+ case 'n':
+ unescVal = unescVal.concat("\n");
+ break;
+ case 'r':
+ unescVal = unescVal.concat("\r");
+ break;
+ case 't':
+ unescVal = unescVal.concat("\t");
+ break;
+ case '\\':
+ unescVal = unescVal.concat("\\");
+ break;
+ case '\"':
+ unescVal = unescVal.concat("\"");
+ break;
+ default:
+ unescVal = unescVal.concat("\\");
+ unescVal = unescVal.concat(value.substring(i + 1, i + 2));
+ break;
+ }
+ i++;
+ }
+ else
+ unescVal = unescVal.concat(value.substring(i, i + 1));
+ }
+ return unescVal;
+ }
+
+ /**
* Parses the archive string and returns a list.
*
* @param the list of archives (comma-separated) in a String.
diff --git a/tools/gnu/classpath/tools/getopt/package.html b/tools/gnu/classpath/tools/getopt/package.html
new file mode 100644
index 000000000..ce6d34a43
--- /dev/null
+++ b/tools/gnu/classpath/tools/getopt/package.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!-- package.html - describes classes in java.util package.
+ Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. -->
+
+<html>
+<head><title>GNU Classpath - gnu.classpath.tools.getopt</title></head>
+
+<body>
+<p>This package contains a GNU-style command line option parser. It
+handles short and long options, options with arguments (optionally
+joined to the option text), and a "long option only" mode. It also
+automatically handles <code>--help</code> output. </p>
+
+</body>
+</html>