diff options
Diffstat (limited to 'gnu/java')
88 files changed, 10629 insertions, 2301 deletions
diff --git a/gnu/java/awt/Buffers.java b/gnu/java/awt/Buffers.java index c6ccf9191..2015634bb 100644 --- a/gnu/java/awt/Buffers.java +++ b/gnu/java/awt/Buffers.java @@ -144,25 +144,7 @@ public final class Buffers */ public static Object getData(DataBuffer buffer) { - if (buffer instanceof DataBufferByte) - return ((DataBufferByte) buffer).getData(); - - if (buffer instanceof DataBufferShort) - return ((DataBufferShort) buffer).getData(); - - if (buffer instanceof DataBufferUShort) - return ((DataBufferUShort) buffer).getData(); - - if (buffer instanceof DataBufferInt) - return ((DataBufferInt) buffer).getData(); - - if (buffer instanceof DataBufferFloat) - return ((DataBufferFloat) buffer).getData(); - - if (buffer instanceof DataBufferDouble) - return ((DataBufferDouble) buffer).getData(); - - throw new ClassCastException("Unknown data buffer type"); + return getData(buffer, 0, null, 0, buffer.getSize()); } @@ -172,46 +154,46 @@ public final class Buffers * given destination array is null. */ public static Object getData(DataBuffer src, int srcOffset, - Object dest, int destOffset, + Object dest, int dstOffset, int length) { Object from; - if (src instanceof DataBufferByte) - { - from = ((DataBufferByte) src).getData(); - if (dest == null) dest = new byte[length+destOffset]; - } - else if (src instanceof DataBufferShort) - { - from = ((DataBufferShort) src).getData(); - if (dest == null) dest = new short[length+destOffset]; - } - else if (src instanceof DataBufferUShort) - { - from = ((DataBufferUShort) src).getData(); - if (dest == null) dest = new short[length+destOffset]; - } - else if (src instanceof DataBufferInt) - { - from = ((DataBufferInt) src).getData(); - if (dest == null) dest = new int[length+destOffset]; - } - else if (src instanceof DataBufferFloat) - { - from = ((DataBufferFloat) src).getData(); - if (dest == null) dest = new float[length+destOffset]; - } - else if (src instanceof DataBufferDouble) - { - from = ((DataBufferDouble) src).getData(); - if (dest == null) dest = new double[length+destOffset]; - } - else + switch(src.getDataType()) { + case DataBuffer.TYPE_BYTE: + if (dest == null) dest = new byte[length+dstOffset]; + for(int i = 0; i < length; i++) + ((byte[])dest)[i + dstOffset] = (byte)src.getElem(i + srcOffset); + break; + + case DataBuffer.TYPE_DOUBLE: + if (dest == null) dest = new double[length+dstOffset]; + for(int i = 0; i < length; i++) + ((double[])dest)[i + dstOffset] = src.getElemDouble(i + srcOffset); + break; + + case DataBuffer.TYPE_FLOAT: + if (dest == null) dest = new float[length+dstOffset]; + for(int i = 0; i < length; i++) + ((float[])dest)[i + dstOffset] = src.getElemFloat(i + srcOffset); + break; + + case DataBuffer.TYPE_INT: + if (dest == null) dest = new int[length+dstOffset]; + for(int i = 0; i < length; i++) + ((int[])dest)[i + dstOffset] = src.getElem(i + srcOffset); + break; + + case DataBuffer.TYPE_SHORT: + case DataBuffer.TYPE_USHORT: + if (dest == null) dest = new short[length+dstOffset]; + for(int i = 0; i < length; i++) + ((short[])dest)[i + dstOffset] = (short)src.getElem(i + srcOffset); + break; + + case DataBuffer.TYPE_UNDEFINED: throw new ClassCastException("Unknown data buffer type"); } - - System.arraycopy(from, srcOffset, dest, destOffset, length); return dest; } diff --git a/gnu/java/awt/font/opentype/truetype/VirtualMachine.java b/gnu/java/awt/font/opentype/truetype/VirtualMachine.java index 6f53af672..7e50b6678 100644 --- a/gnu/java/awt/font/opentype/truetype/VirtualMachine.java +++ b/gnu/java/awt/font/opentype/truetype/VirtualMachine.java @@ -1066,6 +1066,10 @@ class VirtualMachine stack[sp] = ((e1 != 0) || (stack[sp] != 0)) ? 1 : 0; break; + case 0x5C: // NOT + stack[sp] = (stack[sp] != 0) ? 0 : 1; + break; + case 0x5e: // SDB, Set Delta Base in the graphics state deltaBase = stack[sp--]; break; @@ -1764,7 +1768,7 @@ class VirtualMachine /* 50 */ "LT", "LTEQ", "GT", "GTEQ", /* 54 */ "EQ", "NEQ", "INST_56", "INST_57", /* 58 */ "IF", "EIF", "AND", "OR", - /* 5c */ "INST_5C", "INST_5D", "SDB", "SDS", + /* 5c */ "NOT", "INST_5D", "SDB", "SDS", /* 60 */ "ADD", "SUB", "DIV", "MUL", /* 64 */ "ABS", "NEG", "FLOOR", "CEILING", /* 68 */ "ROUND[0]", "ROUND[1]", "ROUND[2]", "ROUND[3]", diff --git a/gnu/java/awt/java2d/AbstractGraphics2D.java b/gnu/java/awt/java2d/AbstractGraphics2D.java index 6408a264d..dd1865cdf 100644 --- a/gnu/java/awt/java2d/AbstractGraphics2D.java +++ b/gnu/java/awt/java2d/AbstractGraphics2D.java @@ -1332,8 +1332,8 @@ public abstract class AbstractGraphics2D { AffineTransform t = new AffineTransform(); t.translate(x, y); - double scaleX = (double) image.getWidth(observer) / (double) width; - double scaleY = (double) image.getHeight(observer) / (double) height; + double scaleX = (double) width / (double) image.getWidth(observer); + double scaleY = (double) height / (double) image.getHeight(observer); t.scale(scaleX, scaleY); return drawImage(image, t, observer); } @@ -1474,15 +1474,11 @@ public abstract class AbstractGraphics2D antialias = (v == RenderingHints.VALUE_ANTIALIAS_ON); } - double offs = 0.5; - if (antialias) - offs = offs / AA_SAMPLING; - Rectangle2D userBounds = s.getBounds2D(); Rectangle2D deviceBounds = new Rectangle2D.Double(); - ArrayList segs = getSegments(s, transform, deviceBounds, false, offs); + ArrayList segs = getSegments(s, transform, deviceBounds, false); Rectangle2D clipBounds = new Rectangle2D.Double(); - ArrayList clipSegs = getSegments(clip, transform, clipBounds, true, offs); + ArrayList clipSegs = getSegments(clip, transform, clipBounds, true); segs.addAll(clipSegs); Rectangle2D inclClipBounds = new Rectangle2D.Double(); Rectangle2D.union(clipBounds, deviceBounds, inclClipBounds); @@ -1677,7 +1673,10 @@ public abstract class AbstractGraphics2D // Scan all relevant lines. int minYInt = (int) Math.ceil(icMinY); - for (int y = minYInt; y <= maxY; y++) + + Rectangle devClip = getDeviceBounds(); + int scanlineMax = (int) Math.min(maxY, devClip.getMaxY()); + for (int y = minYInt; y < scanlineMax; y++) { ArrayList bucket = edgeTable[y - minYInt]; // Update all the x intersections in the current activeEdges table @@ -2170,8 +2169,7 @@ public abstract class AbstractGraphics2D * @return a list of PolyEdge that form the shape in device space */ private ArrayList getSegments(Shape s, AffineTransform t, - Rectangle2D deviceBounds, boolean isClip, - double offs) + Rectangle2D deviceBounds, boolean isClip) { // Flatten the path. TODO: Determine the best flattening factor // wrt to speed and quality. @@ -2214,14 +2212,14 @@ public abstract class AbstractGraphics2D else if (segType == PathIterator.SEG_CLOSE) { // Close the polyline. - PolyEdge edge = new PolyEdge(segX, segY - offs, - polyX, polyY - offs, isClip); + PolyEdge edge = new PolyEdge(segX, segY, + polyX, polyY, isClip); segs.add(edge); } else if (segType == PathIterator.SEG_LINETO) { - PolyEdge edge = new PolyEdge(segX, segY - offs, - seg[0], seg[1] - offs, isClip); + PolyEdge edge = new PolyEdge(segX, segY, + seg[0], seg[1], isClip); segs.add(edge); segX = seg[0]; segY = seg[1]; diff --git a/gnu/java/awt/java2d/PolyEdge.java b/gnu/java/awt/java2d/PolyEdge.java index 8dbdbabcb..6c3b54688 100644 --- a/gnu/java/awt/java2d/PolyEdge.java +++ b/gnu/java/awt/java2d/PolyEdge.java @@ -118,6 +118,7 @@ public class PolyEdge public String toString() { return "Edge: " + x0 + ", " + y0 + ", " + x1 + ", " + y1 + ", slope: " - + slope + ", xIntersection: " + xIntersection; + + slope + ", xIntersection: " + xIntersection + + ", isClip: " + isClip; } } diff --git a/gnu/java/awt/java2d/TexturePaintContext.java b/gnu/java/awt/java2d/TexturePaintContext.java new file mode 100644 index 000000000..1a782ce07 --- /dev/null +++ b/gnu/java/awt/java2d/TexturePaintContext.java @@ -0,0 +1,205 @@ +/* TexturePaintContext.java -- PaintContext impl for TexturePaint + 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.java2d; + +import java.awt.AWTError; +import java.awt.PaintContext; +import java.awt.Rectangle; +import java.awt.TexturePaint; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +/** + * A {@link PaintContext} implementation for {@link TexturePaint}, done in + * pure Java. + * + * @author Roman Kennke (kennke@aicas.com) + */ +public class TexturePaintContext + implements PaintContext +{ + + /** + * The TexturePaint object. + */ + private BufferedImage image; + + /** + * The Raster that holds the texture. + */ + private WritableRaster paintRaster; + + /** + * The transform from userspace into device space. + */ + private AffineTransform transform; + + /** + * Creates a new TexturePaintContext for the specified TexturePaint object. + * This initializes the Raster which is returned by + * {@link #getRaster(int, int, int, int)}. + * + * @param t the texture paint object + * @param db the bounds of the target raster in device space + * @param ub the bounds of the target raster in user space + * @param xform the transformation from user space to device space + */ + public TexturePaintContext(TexturePaint t, Rectangle db, + Rectangle2D ub, AffineTransform xform) + { + image = t.getImage(); + + try + { + // Prepare transform for mapping from device space into image space. + // In order to achieve this we take the transform for userspace-> + // devicespace, append the correct scale and translation according + // to the anchor (which gives us the image->userspace->devicespace + // transform), and invert that (which gives use the device->user->image + // transform). + Rectangle2D anchor = t.getAnchorRect(); + BufferedImage image = t.getImage(); + double scaleX = anchor.getWidth() / image.getWidth(); + double scaleY = anchor.getHeight() / image.getHeight(); + transform = (AffineTransform) xform.clone(); + transform.scale(scaleX, scaleY); + transform.translate(-anchor.getMinX(), -anchor.getMaxX()); + transform = transform.createInverse(); + } + catch (NoninvertibleTransformException ex) + { + AWTError err = + new AWTError("Unexpected NoninvertibleTransformException"); + err.initCause(ex); + throw err; + } + } + + /** + * Disposes the PaintContext. Nothing is to do here, since we don't use + * any native resources in that implementation. + */ + public void dispose() + { + // Nothing to do here. + } + + /** + * Returns the color model of this PaintContext. This implementation returnes + * the color model used by the BufferedImage in the TexturePaint object, + * this avoids costly color model transformations (at least at this point). + * + * @return the color model of this PaintContext + */ + public ColorModel getColorModel() + { + return image.getColorModel(); + } + + /** + * Returns the Raster that is used for painting. + * + * @param x1 the x location of the raster inside the user bounds of this paint + * context + * @param y1 the y location of the raster inside the user bounds of this paint + * context + * @param w the width + * @param h the height + * + * @return the Raster that is used for painting + * + */ + public Raster getRaster(int x1, int y1, int w, int h) + { + ensureRasterSize(w, h); + int x2 = x1 + w; + int y2 = y1 + h; + float[] src = new float[2]; + float[] dest = new float[2]; + Raster source = image.getData(); + int minX = source.getMinX(); + int width = source.getWidth(); + int minY = source.getMinY(); + int height = source.getHeight(); + Object pixel = null; + for (int y = y1; y < y2; y++) + { + for (int x = x1; x < x2; x++) + { + // Transform the coordinates from device space into image space. + src[0] = x; + src[1] = y; + transform.transform(src, 0, dest, 0, 1); + int dx = (int) dest[0]; + int dy = (int) dest[1]; + + // The modulo operation gives us the replication effect. + dx = ((dx - minX) % width) + minX; + dy = ((dy - minY) % height) + minY; + + // Copy the pixel. + pixel = source.getDataElements(dx, dy, pixel); + paintRaster.setDataElements(x - x1, y - y1, pixel); + } + } + return paintRaster; + } + + /** + * Ensures that the target raster exists and has at least the specified + * size. + * + * @param w the requested target width + * @param h the requested target height + */ + private void ensureRasterSize(int w, int h) + { + if (paintRaster == null || paintRaster.getWidth() < w + || paintRaster.getHeight() < h) + { + Raster s = image.getData(); + paintRaster = s.createCompatibleWritableRaster(w, h); + } + } +} diff --git a/gnu/java/awt/peer/gtk/BufferedImageGraphics.java b/gnu/java/awt/peer/gtk/BufferedImageGraphics.java new file mode 100644 index 000000000..d9d300d91 --- /dev/null +++ b/gnu/java/awt/peer/gtk/BufferedImageGraphics.java @@ -0,0 +1,258 @@ +/* BufferedImageGraphics.java + 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.peer.gtk; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.awt.image.ColorModel; +import java.awt.image.DirectColorModel; +import java.awt.image.RenderedImage; +import java.awt.image.ImageObserver; +import java.util.WeakHashMap; + +/** + * Implementation of Graphics2D on a Cairo surface. + * + * Simutanously maintains a CairoSurface and updates the + * BufferedImage from that after each drawing operation. + */ +public class BufferedImageGraphics extends CairoGraphics2D +{ + /** + * the buffered Image. + */ + private BufferedImage image; + + /** + * Image size. + */ + private int imageWidth, imageHeight; + + /** + * The cairo surface that we actually draw on. + */ + CairoSurface surface; + + /** + * Cache BufferedImageGraphics surfaces. + */ + static WeakHashMap bufferedImages = new WeakHashMap(); + + /** + * Its corresponding cairo_t. + */ + private long cairo_t; + + /** + * Colormodels we recognize for fast copying. + */ + static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF); + static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, + 0xFF000000); + private boolean hasFastCM; + private boolean hasAlpha; + + + public BufferedImageGraphics(BufferedImage bi) + { + this.image = bi; + imageWidth = bi.getWidth(); + imageHeight = bi.getHeight(); + if(bi.getColorModel().equals(rgb32)) + { + hasFastCM = true; + hasAlpha = false; + } + else if(bi.getColorModel().equals(argb32)) + { + hasFastCM = true; + hasAlpha = false; + } + else + hasFastCM = false; + + // Cache surfaces. + if( bufferedImages.get( bi ) != null ) + surface = (CairoSurface)bufferedImages.get( bi ); + else + { + surface = new CairoSurface( imageWidth, imageHeight ); + bufferedImages.put(bi, surface); + } + + cairo_t = surface.newCairoContext(); + + DataBuffer db = bi.getRaster().getDataBuffer(); + int[] pixels; + // get pixels + + if(db instanceof CairoSurface) + pixels = ((CairoSurface)db).getPixels(imageWidth * imageHeight); + else + { + if( hasFastCM ) + { + pixels = ((DataBufferInt)db).getData(); + if( !hasAlpha ) + for(int i = 0; i < pixels.length; i++) + pixels[i] &= 0xFFFFFFFF; + } + else + { + pixels = CairoGraphics2D.findSimpleIntegerArray + (image.getColorModel(),image.getData()); + } + } + surface.setPixels( pixels ); + + setup( cairo_t ); + setClip(0, 0, imageWidth, imageHeight); + } + + BufferedImageGraphics(BufferedImageGraphics copyFrom) + { + surface = copyFrom.surface; + cairo_t = surface.newCairoContext(); + imageWidth = copyFrom.imageWidth; + imageHeight = copyFrom.imageHeight; + copy( copyFrom, cairo_t ); + setClip(0, 0, surface.width, surface.height); + } + + /** + * Update a rectangle of the bufferedImage. This can be improved upon a lot. + */ + private void updateBufferedImage(int x, int y, int width, int height) + { + int[] pixels = surface.getPixels(imageWidth * imageHeight); + + if( x > imageWidth || y > imageHeight ) + return; + // Clip edges. + if( x < 0 ){ width = width + x; x = 0; } + if( y < 0 ){ height = height + y; y = 0; } + if( x + width > imageWidth ) + width = imageWidth - x; + if( y + height > imageHeight ) + height = imageHeight - y; + + if( !hasFastCM ) + image.setRGB(x, y, width, height, pixels, + x + y * imageWidth, imageWidth); + else + System.arraycopy(pixels, y * imageWidth, + ((DataBufferInt)image.getRaster().getDataBuffer()). + getData(), y * imageWidth, height * imageWidth); + } + + /** + * Abstract methods. + */ + public Graphics create() + { + return new BufferedImageGraphics(this); + } + + public GraphicsConfiguration getDeviceConfiguration() + { + return null; + } + + protected Rectangle2D getRealBounds() + { + return new Rectangle2D.Double(0.0, 0.0, imageWidth, imageHeight); + } + + public void copyAreaImpl(int x, int y, int width, int height, int dx, int dy) + { + surface.copyAreaNative(x, y, width, height, dx, dy, surface.width); + updateBufferedImage(x + dx, y + dy, width, height); + } + + /** + * Overloaded methods that do actual drawing need to enter the gdk threads + * and also do certain things before and after. + */ + public void draw(Shape s) + { + super.draw(s); + Rectangle r = s.getBounds(); + updateBufferedImage(r.x, r.y, r.width, r.height); + } + + public void fill(Shape s) + { + super.fill(s); + Rectangle r = s.getBounds(); + updateBufferedImage(r.x, r.y, r.width, r.height); + } + + public void drawRenderedImage(RenderedImage image, AffineTransform xform) + { + super.drawRenderedImage(image, xform); + updateBufferedImage(0, 0, imageWidth, imageHeight); + } + + protected boolean drawImage(Image img, AffineTransform xform, + Color bgcolor, ImageObserver obs) + { + boolean rv = super.drawImage(img, xform, bgcolor, obs); + updateBufferedImage(0, 0, imageWidth, imageHeight); + return rv; + } + + public void drawGlyphVector(GlyphVector gv, float x, float y) + { + super.drawGlyphVector(gv, x, y); + updateBufferedImage(0, 0, imageWidth, imageHeight); + } +} + diff --git a/gnu/java/awt/peer/gtk/GdkGraphics2D.java b/gnu/java/awt/peer/gtk/CairoGraphics2D.java index ff3555015..a76962afc 100644 --- a/gnu/java/awt/peer/gtk/GdkGraphics2D.java +++ b/gnu/java/awt/peer/gtk/CairoGraphics2D.java @@ -1,5 +1,5 @@ -/* GdkGraphics2D.java -- - Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. +/* CairoGraphics2D.java -- + Copyright (C) 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,14 +38,12 @@ exception statement from your version. */ package gnu.java.awt.peer.gtk; -import gnu.classpath.Configuration; import gnu.java.awt.ClasspathToolkit; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; -import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GradientPaint; @@ -58,27 +56,30 @@ import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Stroke; +import java.awt.Polygon; import java.awt.TexturePaint; import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; +import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; -import java.awt.geom.GeneralPath; +import java.awt.geom.Area; +import java.awt.geom.Line2D; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; -import java.awt.image.CropImageFilter; import java.awt.image.DataBuffer; import java.awt.image.DataBufferInt; import java.awt.image.DirectColorModel; -import java.awt.image.FilteredImageSource; import java.awt.image.ImageObserver; +import java.awt.image.ImageProducer; import java.awt.image.ImagingOpException; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.Raster; @@ -90,73 +91,136 @@ import java.awt.image.renderable.RenderableImage; import java.text.AttributedCharacterIterator; import java.util.HashMap; import java.util.Map; -import java.util.Stack; -public class GdkGraphics2D extends Graphics2D +/** + * This is an abstract implementation of Graphics2D on Cairo. + * + * It should be subclassed for different Cairo contexts. + * + * Note for subclassers: Apart from the constructor (see comments below), + * The following abstract methods must be implemented: + * + * Graphics create() + * GraphicsConfiguration getDeviceConfiguration() + * copyArea(int x, int y, int width, int height, int dx, int dy) + * + * Also, dispose() must be overloaded to free any native datastructures + * used by subclass and in addition call super.dispose() to free the + * native cairographics2d structure and cairo_t. + * + * @author Sven de Marothy + */ +public abstract class CairoGraphics2D extends Graphics2D { - ////////////////////////////////////// - ////// State Management Methods ////// - ////////////////////////////////////// - static { - if (Configuration.INIT_LOAD_LIBRARY) - System.loadLibrary("gtkpeer"); - - initStaticState(); + System.loadLibrary("gtkpeer"); } - - static native void initStaticState(); - - private final int native_state = GtkGenericPeer.getUniqueInteger(); - // These are package-private to avoid accessor methods. + /** + * Important: This is a pointer to the native cairographics2d structure + * + * DO NOT CHANGE WITHOUT CHANGING NATIVE CODE. + */ + long nativePointer; + + // Drawing state variables + /** + * The current paint + */ Paint paint; + + /** + * The current stroke + */ Stroke stroke; - Color fg; - Color bg; + + /* + * Current foreground and background color. + */ + Color fg, bg; + + /** + * Current clip shape. + */ Shape clip; + + /** + * Current transform. + */ AffineTransform transform; - private GtkComponentPeer component; - // This is package-private to avoid an accessor method. + + /** + * Current font. + */ Font font; - private RenderingHints hints; - private BufferedImage bimage; - private boolean pixelConversionRequired; - private int[] pixelBuffer; - // This is package-private to avoid an accessor method. + + /** + * The current compositing context, if any. + */ Composite comp; - private Stack stateStack; - private native void initState(GtkComponentPeer component); - private native void initState(int width, int height); - private native void initState(int[] pixes, int width, int height); - private native void copyState(GdkGraphics2D g); - public native void dispose(); - private native void cairoSurfaceSetFilter(int filter); + /** + * Rendering hint map. + */ + private RenderingHints hints; - public void finalize() - { - dispose(); - } + /** + * Some operations (drawing rather than filling) require that their + * coords be shifted to land on 0.5-pixel boundaries, in order to land on + * "middle of pixel" coordinates and light up complete pixels. + */ + private boolean shiftDrawCalls = false; + + /** + * Keep track if the first clip to be set, which is restored on setClip(null); + */ + private boolean firstClip = true; + private Shape originalClip; + + /** + * Stroke used for 3DRects + */ + private static BasicStroke draw3DRectStroke = new BasicStroke(); + + static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF); + static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, + 0xFF000000); - public Graphics create() + /** + * Constructor does nothing. + */ + public CairoGraphics2D() { - return new GdkGraphics2D(this); } - public Graphics create(int x, int y, int width, int height) - { - return new GdkGraphics2D(this, x, y, width, height); + /** + * Sets up the default values and allocates the native cairographics2d structure + * @param cairo_t_pointer, a native pointer to a cairo_t of the context. + */ + public void setup(long cairo_t_pointer) + { + nativePointer = init(cairo_t_pointer); + setRenderingHints(new RenderingHints(getDefaultHints())); + font = new Font("SansSerif", Font.PLAIN, 12); + setColor(Color.black); + setBackground(Color.white); + setPaint(Color.black); + setStroke(new BasicStroke()); + setTransform(new AffineTransform()); } - GdkGraphics2D(GdkGraphics2D g) + /** + * Same as above, but copies the state of another CairoGraphics2D. + */ + public void copy(CairoGraphics2D g, long cairo_t_pointer) { - Color foreground; - + nativePointer = init(cairo_t_pointer); paint = g.paint; stroke = g.stroke; setRenderingHints(g.hints); + + Color foreground; if (g.fg.getAlpha() != -1) foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(), @@ -184,515 +248,190 @@ public class GdkGraphics2D extends Graphics2D transform = new AffineTransform(g.transform); font = g.font; - component = g.component; - copyState(g); setColor(foreground); setBackground(bg); setPaint(paint); setStroke(stroke); setTransform(transform); - setClip(clip); - stateStack = new Stack(); - } - - GdkGraphics2D(GdkGraphics2D g, int x, int y, int widht, int height) - { - this(g); - translate(x, y); - clipRect(0, 0, widht, height); } - GdkGraphics2D(int width, int height) + /** + * Generic destructor - call the native dispose() method. + */ + public void finalize() { - initState(width, height); - - setColor(Color.black); - setBackground(new Color(0, 0, 0, 0)); - setPaint(getColor()); - setFont(new Font("SansSerif", Font.PLAIN, 12)); - setTransform(new AffineTransform()); - setStroke(new BasicStroke()); - setRenderingHints(getDefaultHints()); - - stateStack = new Stack(); + dispose(); } - GdkGraphics2D(GtkComponentPeer component) + /** + * Disposes the native cairographics2d structure, including the + * cairo_t and any gradient stuff, if allocated. + * Subclasses should of course overload and call this if + * they have additional native structures. + */ + public void dispose() { - this.component = component; - - initComponentGraphics2D(); + disposeNative(nativePointer); + nativePointer = 0; } - void initComponentGraphics2D() - { - initState(component); - - setColor(component.awtComponent.getForeground()); - setBackground(component.awtComponent.getBackground()); - setPaint(getColor()); - setTransform(new AffineTransform()); - setStroke(new BasicStroke()); - setRenderingHints(getDefaultHints()); - setFont(new Font("SansSerif", Font.PLAIN, 12)); + /** + * Allocate the cairographics2d structure and set the cairo_t pointer in it. + * @param pointer - a cairo_t pointer, casted to a long. + */ + private native long init(long pointer); - stateStack = new Stack(); - } + /** + * These are declared abstract as there may be context-specific issues. + */ + public abstract Graphics create(); - GdkGraphics2D(BufferedImage bimage) - { - this.bimage = bimage; - this.pixelBuffer = findSimpleIntegerArray(bimage.getColorModel(), - bimage.getRaster()); - if (this.pixelBuffer == null) - { - this.pixelBuffer = new int[bimage.getRaster().getWidth() * bimage.getRaster() - .getHeight()]; - this.pixelConversionRequired = true; - } - else - { - this.pixelConversionRequired = false; - } + public abstract GraphicsConfiguration getDeviceConfiguration(); - initState(this.pixelBuffer, bimage.getWidth(), bimage.getHeight()); + protected abstract void copyAreaImpl(int x, int y, + int width, int height, int dx, int dy); - setColor(Color.black); - setBackground(new Color(0, 0, 0, 0)); - setPaint(getColor()); - setFont(new Font("SansSerif", Font.PLAIN, 12)); - setTransform(new AffineTransform()); - setStroke(new BasicStroke()); - setRenderingHints(getDefaultHints()); - stateStack = new Stack(); + protected abstract Rectangle2D getRealBounds(); - // draw current buffered image to the pixmap associated - // with it, if the image is not equal to our paint buffer. - if (pixelConversionRequired) - drawImage(bimage, new AffineTransform(1, 0, 0, 1, 0, 0), bg, null); - } + ////// Native Methods //////////////////////////////////////////////////// - //////////////////////////////////// - ////// Native Drawing Methods ////// - //////////////////////////////////// + /** + * Dispose of allocate native resouces. + */ + public native void disposeNative(long pointer); - // GDK drawing methods - private native void gdkDrawDrawable(GdkGraphics2D other, int x, int y); + /** + * Draw pixels as an RGBA int matrix + * @param w, h - width and height + * @param stride - stride of the array width + * @param i2u - affine transform array + */ + private native void drawPixels(long pointer, int[] pixels, int w, int h, + int stride, double[] i2u); - // drawing utility methods - private native void drawPixels(int[] pixels, int w, int h, int stride, - double[] i2u); - private native void setTexturePixels(int[] pixels, int w, int h, int stride); - private native void setGradient(double x1, double y1, double x2, double y2, + private native void setGradient(long pointer, double x1, double y1, + double x2, double y2, int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2, boolean cyclic); - // simple passthroughs to cairo - private native void cairoSave(); - private native void cairoRestore(); - private native void cairoSetMatrix(double[] m); - private native void cairoSetOperator(int cairoOperator); - private native void cairoSetRGBAColor(double red, double green, - double blue, double alpha); - private native void cairoSetFillRule(int cairoFillRule); - private native void cairoSetLineWidth(double width); - private native void cairoSetLineCap(int cairoLineCap); - private native void cairoSetLineJoin(int cairoLineJoin); - private native void cairoSetDash(double[] dashes, int ndash, double offset); - - private native void cairoSetMiterLimit(double limit); - private native void cairoNewPath(); - private native void cairoMoveTo(double x, double y); - private native void cairoLineTo(double x, double y); - private native void cairoCurveTo(double x1, double y1, double x2, double y2, - double x3, double y3); - private native void cairoRelMoveTo(double dx, double dy); - private native void cairoRelLineTo(double dx, double dy); - private native void cairoRelCurveTo(double dx1, double dy1, double dx2, - double dy2, double dx3, double dy3); - private native void cairoRectangle(double x, double y, double width, - double height); - private native void cairoClosePath(); - private native void cairoStroke(); - private native void cairoFill(); - private native void cairoClip(); - - ///////////////////////////////////////////// - ////// General Drawing Support Methods ////// - ///////////////////////////////////////////// - - private class DrawState - { - private Paint paint; - private Stroke stroke; - private Color fg; - private Color bg; - private Shape clip; - private AffineTransform transform; - private Font font; - private Composite comp; - - DrawState(GdkGraphics2D g) - { - this.paint = g.paint; - this.stroke = g.stroke; - this.fg = g.fg; - this.bg = g.bg; - this.clip = g.clip; - if (g.transform != null) - this.transform = (AffineTransform) g.transform.clone(); - this.font = g.font; - this.comp = g.comp; - } - - public void restore(GdkGraphics2D g) - { - g.paint = this.paint; - g.stroke = this.stroke; - g.fg = this.fg; - g.bg = this.bg; - g.clip = this.clip; - g.transform = this.transform; - g.font = this.font; - g.comp = this.comp; - } - } - - private void stateSave() - { - stateStack.push(new DrawState(this)); - cairoSave(); - } - - private void stateRestore() - { - ((DrawState) (stateStack.pop())).restore(this); - cairoRestore(); - } - - // Some operations (drawing rather than filling) require that their - // coords be shifted to land on 0.5-pixel boundaries, in order to land on - // "middle of pixel" coordinates and light up complete pixels. - private boolean shiftDrawCalls = false; - - private double shifted(double coord, boolean doShift) - { - if (doShift) - return Math.floor(coord) + 0.5; - else - return coord; - } + private native void setTexturePixels(long pointer, int[] pixels, int w, + int h, int stride); - private void walkPath(PathIterator p, boolean doShift) - { - double x = 0; - double y = 0; - double[] coords = new double[6]; - - cairoSetFillRule(p.getWindingRule()); - for (; ! p.isDone(); p.next()) - { - int seg = p.currentSegment(coords); - switch (seg) - { - case PathIterator.SEG_MOVETO: - x = shifted(coords[0], doShift); - y = shifted(coords[1], doShift); - cairoMoveTo(x, y); - break; - case PathIterator.SEG_LINETO: - x = shifted(coords[0], doShift); - y = shifted(coords[1], doShift); - cairoLineTo(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 x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x); - double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y); - - x = shifted(coords[2], doShift); - y = shifted(coords[3], doShift); - cairoCurveTo(x1, y1, x2, y2, x, y); - break; - case PathIterator.SEG_CUBICTO: - x = shifted(coords[4], doShift); - y = shifted(coords[5], doShift); - cairoCurveTo(shifted(coords[0], doShift), - shifted(coords[1], doShift), - shifted(coords[2], doShift), - shifted(coords[3], doShift), x, y); - break; - case PathIterator.SEG_CLOSE: - cairoClosePath(); - break; - } - } - } - - private Map getDefaultHints() - { - HashMap defaultHints = new HashMap(); - - defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); - - defaultHints.put(RenderingHints.KEY_STROKE_CONTROL, - RenderingHints.VALUE_STROKE_DEFAULT); - - defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS, - RenderingHints.VALUE_FRACTIONALMETRICS_OFF); - - defaultHints.put(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_OFF); - - defaultHints.put(RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_DEFAULT); - - return defaultHints; - } - - public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster) - { - if (cm == null || raster == null) - return null; - - if (! cm.getColorSpace().isCS_sRGB()) - return null; - - if (! (cm instanceof DirectColorModel)) - return null; - - DirectColorModel dcm = (DirectColorModel) cm; - - if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00 - || dcm.getBlueMask() != 0x000000FF) - return null; - - if (! (raster instanceof WritableRaster)) - return null; - - if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT) - return null; - - if (! (raster.getDataBuffer() instanceof DataBufferInt)) - return null; - - DataBufferInt db = (DataBufferInt) raster.getDataBuffer(); - - if (db.getNumBanks() != 1) - return null; - - // Finally, we have determined that this is a single bank, [A]RGB-int - // buffer in sRGB space. It's worth checking all this, because it means - // that cairo can paint directly into the data buffer, which is very - // fast compared to all the normal copying and converting. - - return db.getData(); - } + /** + * Set the current transform matrix + */ + private native void cairoSetMatrix(long pointer, double[] m); - private void updateBufferedImage() - { - if (bimage != null && pixelConversionRequired) - { - int height = bimage.getHeight(); - int width = bimage.getWidth(); - int index = 0; - for (int y = 0; y < height; ++y) - for (int x = 0; x < width; ++x) - bimage.setRGB(x, y, pixelBuffer[index++]); - } - } + /** + * Set the compositing operator + */ + private native void cairoSetOperator(long pointer, int cairoOperator); - private boolean drawImage(Image img, AffineTransform xform, - Color bgcolor, ImageObserver obs) - { - if (img == null) - return false; + /** + * Sets the current color in RGBA as a 0.0-1.0 double + */ + private native void cairoSetRGBAColor(long pointer, double red, double green, + double blue, double alpha); - // FIXME: I'll fix this, /Sven -// if (img instanceof GtkOffScreenImage -// && img.getGraphics() instanceof GdkGraphics2D -// && (xform == null || xform.getType() == AffineTransform.TYPE_IDENTITY -// || xform.getType() == AffineTransform.TYPE_TRANSLATION)) -// { -// // we are being asked to flush a double buffer from Gdk -// GdkGraphics2D g2 = (GdkGraphics2D) img.getGraphics(); -// gdkDrawDrawable(g2, (int) xform.getTranslateX(), -// (int) xform.getTranslateY()); - -// updateBufferedImage(); - -// return true; -// } -// else - { - // In this case, xform is an AffineTransform that transforms bounding - // box of the specified image from image space to user space. However - // when we pass this transform to cairo, cairo will use this transform - // to map "user coordinates" to "pixel" coordinates, which is the - // other way around. Therefore to get the "user -> pixel" transform - // that cairo wants from "image -> user" transform that we currently - // have, we will need to invert the transformation matrix. - AffineTransform invertedXform = new AffineTransform(); + /** + * Sets the current winding rule in Cairo + */ + private native void cairoSetFillRule(long pointer, int cairoFillRule); - try - { - invertedXform = xform.createInverse(); - if (img instanceof BufferedImage) - { - // draw an image which has actually been loaded - // into memory fully - BufferedImage b = (BufferedImage) img; - return drawRaster(b.getColorModel(), b.getTile(0, 0), - invertedXform, bgcolor); - } - else - return this.drawImage(GdkPixbufDecoder.createBufferedImage(img - .getSource()), - xform, bgcolor, obs); - } - catch (NoninvertibleTransformException e) - { - throw new ImagingOpException("Unable to invert transform " - + xform.toString()); - } - } - } + /** + * Set the line style, cap, join and miter limit. + * Cap and join parameters are in the BasicStroke enumerations. + */ + private native void cairoSetLine(long pointer, double width, int cap, + int join, double miterLimit); - ////////////////////////////////////////////////// - ////// Implementation of Graphics2D Methods ////// - ////////////////////////////////////////////////// + /** + * Set the dash style + */ + private native void cairoSetDash(long pointer, double[] dashes, int ndash, + double offset); - public void draw(Shape s) - { - if (stroke != null && ! (stroke instanceof BasicStroke)) - { - fill(stroke.createStrokedShape(s)); - return; - } + /* + * Draws a Glyph Vector + */ + native void cairoDrawGlyphVector(long pointer, GdkFontPeer font, + float x, float y, int n, + int[] codes, float[] positions); - cairoNewPath(); - if (s instanceof Rectangle2D) - { - Rectangle2D r = (Rectangle2D) s; - cairoRectangle(shifted(r.getX(), shiftDrawCalls), - shifted(r.getY(), shiftDrawCalls), r.getWidth(), - r.getHeight()); - } - else - walkPath(s.getPathIterator(null), shiftDrawCalls); - cairoStroke(); + private native void cairoRelCurveTo(long pointer, double dx1, double dy1, + double dx2, double dy2, double dx3, + double dy3); - updateBufferedImage(); - } + /** + * Appends a rectangle to the current path + */ + private native void cairoRectangle(long pointer, double x, double y, + double width, double height); - public void fill(Shape s) - { - cairoNewPath(); - if (s instanceof Rectangle2D) - { - Rectangle2D r = (Rectangle2D) s; - cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight()); - } - else - walkPath(s.getPathIterator(null), false); + /** + * New current path + */ + private native void cairoNewPath(long pointer); - cairoFill(); + /** + * Close current path + */ + private native void cairoClosePath(long pointer); - updateBufferedImage(); - } + /** moveTo */ + private native void cairoMoveTo(long pointer, double x, double y); - public void clip(Shape s) - { - // update it - if (clip == null || s == null) - clip = s; - else if (s instanceof Rectangle2D && clip instanceof Rectangle2D) - { - Rectangle2D r = (Rectangle2D) s; - Rectangle2D curr = (Rectangle2D) clip; - clip = curr.createIntersection(r); - } - else - throw new UnsupportedOperationException(); + /** relative moveTo */ + private native void cairoRelMoveTo(long pointer, double dx, double dy); - // draw it - if (clip != null) - { - cairoNewPath(); - if (clip instanceof Rectangle2D) - { - Rectangle2D r = (Rectangle2D) clip; - cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight()); - } - else - walkPath(clip.getPathIterator(null), false); + /** lineTo */ + private native void cairoLineTo(long pointer, double x, double y); - // cairoClosePath (); - cairoClip(); - } - } + /** relative lineTo */ + private native void cairoRelLineTo(long pointer, double dx, double dy); - public Paint getPaint() - { - return paint; - } + /** Cubic curve-to */ + private native void cairoCurveTo(long pointer, double x1, double y1, + double x2, double y2, + double x3, double y3); - public AffineTransform getTransform() - { - return (AffineTransform) transform.clone(); - } + /** + * Stroke current path + */ + private native void cairoStroke(long pointer); - public void setPaint(Paint p) - { - if (paint == null) - return; + /** + * Fill current path + */ + private native void cairoFill(long pointer); - paint = p; - if (paint instanceof Color) - { - setColor((Color) paint); - } - else if (paint instanceof TexturePaint) - { - TexturePaint tp = (TexturePaint) paint; - BufferedImage img = tp.getImage(); + /** + * Clip current path + */ + private native void cairoClip(long pointer); - // map the image to the anchor rectangle - int width = (int) tp.getAnchorRect().getWidth(); - int height = (int) tp.getAnchorRect().getHeight(); + /** + * Save clip + */ + private native void cairoPreserveClip(long pointer); - double scaleX = width / (double) img.getWidth(); - double scaleY = width / (double) img.getHeight(); + /** + * Save clip + */ + private native void cairoResetClip(long pointer); - AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0); - AffineTransformOp op = new AffineTransformOp(at, getRenderingHints()); - BufferedImage texture = op.filter(img, null); - int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width); - setTexturePixels(pixels, width, height, width); - } - else if (paint instanceof GradientPaint) - { - GradientPaint gp = (GradientPaint) paint; - Point2D p1 = gp.getPoint1(); - Point2D p2 = gp.getPoint2(); - Color c1 = gp.getColor1(); - Color c2 = gp.getColor2(); - setGradient(p1.getX(), p1.getY(), p2.getX(), p2.getY(), c1.getRed(), - c1.getGreen(), c1.getBlue(), c1.getAlpha(), c2.getRed(), - c2.getGreen(), c2.getBlue(), c2.getAlpha(), gp.isCyclic()); - } - else - throw new java.lang.UnsupportedOperationException(); - } + /** + * Set interpolation types + */ + private native void cairoSurfaceSetFilter(long pointer, int filter); + ///////////////////////// TRANSFORMS /////////////////////////////////// + /** + * Set the current transform + */ public void setTransform(AffineTransform tx) { transform = tx; @@ -700,10 +439,10 @@ public class GdkGraphics2D extends Graphics2D { double[] m = new double[6]; transform.getMatrix(m); - cairoSetMatrix(m); + cairoSetMatrix(nativePointer, m); } } - + public void transform(AffineTransform tx) { if (transform == null) @@ -717,10 +456,10 @@ public class GdkGraphics2D extends Graphics2D // rather than degrade to bounds. Rectangle2D r = clip.getBounds2D(); double[] coords = new double[] - { - r.getX(), r.getY(), r.getX() + r.getWidth(), - r.getY() + r.getHeight() - }; + { + r.getX(), r.getY(), r.getX() + r.getWidth(), + r.getY() + r.getHeight() + }; try { tx.createInverse().transform(coords, 0, coords, 0, 2); @@ -748,14 +487,13 @@ public class GdkGraphics2D extends Graphics2D { transform(AffineTransform.getScaleInstance(sx, sy)); } - + /** * Translate the system of the co-ordinates. As translation is a frequent * operation, it is done in an optimised way, unlike scaling and rotating. */ public void translate(double tx, double ty) { - // 200 -> 140 if (transform != null) transform.translate(tx, ty); else @@ -778,7 +516,7 @@ public class GdkGraphics2D extends Graphics2D setTransform(transform); } - + public void translate(int x, int y) { translate((double) x, (double) y); @@ -789,6 +527,102 @@ public class GdkGraphics2D extends Graphics2D transform(AffineTransform.getShearInstance(shearX, shearY)); } + ///////////////////////// DRAWING STATE /////////////////////////////////// + + public void clip(Shape s) + { + // Do not touch clip when s == null. + if (s == null) + return; + + // If the current clip is still null, initialize it. + if (clip == null) + clip = originalClip; + + // This is so common, let's optimize this. + if (clip instanceof Rectangle2D && s instanceof Rectangle2D) + { + Rectangle2D clipRect = (Rectangle2D) clip; + Rectangle2D r = (Rectangle2D) s; + Rectangle2D.intersect(clipRect, r, clipRect); + // Call setClip so that subclasses get notified. + setClip(clipRect); + } + else + { + Area current; + if (clip instanceof Area) + current = (Area) clip; + else + current = new Area(clip); + + Area intersect; + if (s instanceof Area) + intersect = (Area) s; + else + intersect = new Area(s); + + current.intersect(intersect); + clip = current; + // Call setClip so that the native side gets notified. + setClip(clip); + } + } + + public Paint getPaint() + { + return paint; + } + + public AffineTransform getTransform() + { + return (AffineTransform) transform.clone(); + } + + public void setPaint(Paint p) + { + if (paint == null) + return; + + paint = p; + if (paint instanceof Color) + { + setColor((Color) paint); + } + else if (paint instanceof TexturePaint) + { + TexturePaint tp = (TexturePaint) paint; + BufferedImage img = tp.getImage(); + + // map the image to the anchor rectangle + int width = (int) tp.getAnchorRect().getWidth(); + int height = (int) tp.getAnchorRect().getHeight(); + + double scaleX = (width+1) / (double) img.getWidth(); + double scaleY = (height+1) / (double) img.getHeight(); + + AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0); + AffineTransformOp op = new AffineTransformOp(at, getRenderingHints()); + BufferedImage texture = op.filter(img, null); + int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width); + setTexturePixels(nativePointer, pixels, width, height, width); + } + else if (paint instanceof GradientPaint) + { + GradientPaint gp = (GradientPaint) paint; + Point2D p1 = gp.getPoint1(); + Point2D p2 = gp.getPoint2(); + Color c1 = gp.getColor1(); + Color c2 = gp.getColor2(); + setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(), + c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(), + c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(), + gp.isCyclic()); + } + else + throw new java.lang.UnsupportedOperationException(); + } + public Stroke getStroke() { return stroke; @@ -800,28 +634,23 @@ public class GdkGraphics2D extends Graphics2D if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; - cairoSetLineCap(bs.getEndCap()); - cairoSetLineWidth(bs.getLineWidth()); - cairoSetLineJoin(bs.getLineJoin()); - cairoSetMiterLimit(bs.getMiterLimit()); + cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(), + bs.getLineJoin(), bs.getMiterLimit()); + float[] dashes = bs.getDashArray(); if (dashes != null) { double[] double_dashes = new double[dashes.length]; for (int i = 0; i < dashes.length; i++) double_dashes[i] = dashes[i]; - cairoSetDash(double_dashes, double_dashes.length, + cairoSetDash(nativePointer, double_dashes, double_dashes.length, (double) bs.getDashPhase()); } else - cairoSetDash(new double[0], 0, 0.0); + cairoSetDash(nativePointer, new double[0], 0, 0.0); } } - //////////////////////////////////////////////// - ////// Implementation of Graphics Methods ////// - //////////////////////////////////////////////// - public void setPaintMode() { setComposite(AlphaComposite.SrcOver); @@ -836,14 +665,22 @@ public class GdkGraphics2D extends Graphics2D { if (c == null) c = Color.BLACK; - - if (c.equals(fg)) - return; fg = c; paint = c; - cairoSetRGBAColor(fg.getRed() / 255.0, fg.getGreen() / 255.0, - fg.getBlue() / 255.0, fg.getAlpha() / 255.0); + updateColor(); + } + + /** + * Set the current fg value as the cairo color. + */ + void updateColor() + { + if (fg == null) + fg = Color.BLACK; + cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0, + fg.getGreen() / 255.0,fg.getBlue() / 255.0, + fg.getAlpha() / 255.0); } public Color getColor() @@ -894,81 +731,41 @@ public class GdkGraphics2D extends Graphics2D public void setClip(int x, int y, int width, int height) { - setClip(new Rectangle2D.Double((double) x, (double) y, (double) width, - (double) height)); + if( width < 0 || height < 0 ) + return; + + setClip(new Rectangle2D.Double(x, y, width, height)); } public void setClip(Shape s) - { - clip = s; - if (clip == null) + { + // The first time the clip is set, save it as the original clip + // to reset to on s == null. We can rely on this being non-null + // because the constructor in subclasses is expected to set the + // initial clip properly. + if( firstClip ) { - // Reset clipping. - if (component != null) - { - Dimension d = component.awtComponent.getSize(); - setClip(0, 0, d.width, d.height); - } + originalClip = s; + firstClip = false; } + + if (s == null) + clip = originalClip; else - { - cairoNewPath(); - if (s instanceof Rectangle2D) - { - Rectangle2D r = (Rectangle2D) s; - cairoRectangle(r.getX(), r.getY(), r.getWidth(), r.getHeight()); - } - else - walkPath(s.getPathIterator(null), false); + clip = s; + cairoResetClip(nativePointer); - // cairoClosePath (); - cairoClip(); + cairoNewPath(nativePointer); + if (clip instanceof Rectangle2D) + { + Rectangle2D r = (Rectangle2D) clip; + cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(), + r.getHeight()); } - } - - private static BasicStroke draw3DRectStroke = new BasicStroke(); - - public void draw3DRect(int x, int y, int width, int height, boolean raised) - { - Stroke tmp = stroke; - setStroke(draw3DRectStroke); - super.draw3DRect(x, y, width, height, raised); - setStroke(tmp); - updateBufferedImage(); - } - - public void fill3DRect(int x, int y, int width, int height, boolean raised) - { - Stroke tmp = stroke; - setStroke(draw3DRectStroke); - super.fill3DRect(x, y, width, height, raised); - setStroke(tmp); - updateBufferedImage(); - } - - public void drawRect(int x, int y, int width, int height) - { - draw(new Rectangle(x, y, width, height)); - } - - public void fillRect(int x, int y, int width, int height) - { - cairoNewPath(); - cairoRectangle(x, y, width, height); - cairoFill(); - } - - public void clearRect(int x, int y, int width, int height) - { - if (bg != null) - cairoSetRGBAColor(bg.getRed() / 255.0, bg.getGreen() / 255.0, - bg.getBlue() / 255.0, 1.0); - cairoNewPath(); - cairoRectangle(x, y, width, height); - cairoFill(); - setColor(fg); - - updateBufferedImage(); + else + walkPath(clip.getPathIterator(null), false); + + cairoClip(nativePointer); } public void setBackground(Color c) @@ -983,196 +780,228 @@ public class GdkGraphics2D extends Graphics2D return bg; } - private void doPolygon(int[] xPoints, int[] yPoints, int nPoints, - boolean close, boolean fill) + /** + * Return the current composite. + */ + public Composite getComposite() { - if (nPoints < 1) - return; - GeneralPath gp = new GeneralPath(PathIterator.WIND_EVEN_ODD); - gp.moveTo((float) xPoints[0], (float) yPoints[0]); - for (int i = 1; i < nPoints; i++) - gp.lineTo((float) xPoints[i], (float) yPoints[i]); + if (comp == null) + return AlphaComposite.SrcOver; + else + return comp; + } - if (close) - gp.closePath(); + /** + * Sets the current composite context. + */ + public void setComposite(Composite comp) + { + this.comp = comp; - Shape sh = gp; - if (fill == false && stroke != null && ! (stroke instanceof BasicStroke)) + if (comp instanceof AlphaComposite) { - sh = stroke.createStrokedShape(gp); - fill = true; + AlphaComposite a = (AlphaComposite) comp; + cairoSetOperator(nativePointer, a.getRule()); + Color c = getColor(); + setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), + (int) (a.getAlpha() * ((float) c.getAlpha())))); } - - if (fill) - fill(sh); else - draw(sh); + { + // FIXME: implement general Composite support + throw new java.lang.UnsupportedOperationException(); + } } - public void drawLine(int x1, int y1, int x2, int y2) + ///////////////////////// DRAWING PRIMITIVES /////////////////////////////////// + + public void draw(Shape s) { - int[] xp = new int[2]; - int[] yp = new int[2]; + if (stroke != null && ! (stroke instanceof BasicStroke)) + { + fill(stroke.createStrokedShape(s)); + return; + } - xp[0] = x1; - xp[1] = x2; - yp[0] = y1; - yp[1] = y2; + cairoNewPath(nativePointer); - doPolygon(xp, yp, 2, false, false); + if (s instanceof Rectangle2D) + { + Rectangle2D r = (Rectangle2D) s; + cairoRectangle(nativePointer, shifted(r.getX(), shiftDrawCalls), + shifted(r.getY(), shiftDrawCalls), r.getWidth(), + r.getHeight()); + } + else + walkPath(s.getPathIterator(null), shiftDrawCalls); + cairoStroke(nativePointer); } - public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) + public void fill(Shape s) { - doPolygon(xPoints, yPoints, nPoints, true, true); + cairoNewPath(nativePointer); + if (s instanceof Rectangle2D) + { + Rectangle2D r = (Rectangle2D) s; + cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(), + r.getHeight()); + } + else + walkPath(s.getPathIterator(null), false); + + cairoFill(nativePointer); } - public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) + /** + * Note that the rest of the drawing methods go via fill() or draw() for the drawing, + * although subclasses may with to overload these methods where context-specific + * optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int) + */ + + public void clearRect(int x, int y, int width, int height) { - doPolygon(xPoints, yPoints, nPoints, true, false); + if (bg != null) + cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0, + bg.getGreen() / 255.0, bg.getBlue() / 255.0, 1.0); + fillRect(x, y, width, height); + updateColor(); } - public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) + public void draw3DRect(int x, int y, int width, int height, boolean raised) { - doPolygon(xPoints, yPoints, nPoints, false, false); + Stroke tmp = stroke; + setStroke(draw3DRectStroke); + super.draw3DRect(x, y, width, height, raised); + setStroke(tmp); } - private boolean drawRaster(ColorModel cm, Raster r, - AffineTransform imageToUser, Color bgcolor) + public void drawArc(int x, int y, int width, int height, int startAngle, + int arcAngle) { - if (r == null) - return false; - - SampleModel sm = r.getSampleModel(); - DataBuffer db = r.getDataBuffer(); - - if (db == null || sm == null) - return false; - - if (cm == null) - cm = ColorModel.getRGBdefault(); - - double[] i2u = new double[6]; - if (imageToUser != null) - imageToUser.getMatrix(i2u); - else - { - i2u[0] = 1; - i2u[1] = 0; - i2u[2] = 0; - i2u[3] = 1; - i2u[4] = 0; - i2u[5] = 0; - } - - int[] pixels = findSimpleIntegerArray(cm, r); - - if (pixels == null) - { - // FIXME: I don't think this code will work correctly with a non-RGB - // MultiPixelPackedSampleModel. Although this entire method should - // probably be rewritten to better utilize Cairo's different supported - // data formats. - if (sm instanceof MultiPixelPackedSampleModel) - { - pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels); - for (int i = 0; i < pixels.length; i++) - pixels[i] = cm.getRGB(pixels[i]); - } - else - { - pixels = new int[r.getWidth() * r.getHeight()]; - for (int i = 0; i < pixels.length; i++) - pixels[i] = cm.getRGB(db.getElem(i)); - } - } - - // Change all transparent pixels in the image to the specified bgcolor, - // or (if there's no alpha) fill in an alpha channel so that it paints - // correctly. - if (cm.hasAlpha()) - { - if (bgcolor != null && cm.hasAlpha()) - for (int i = 0; i < pixels.length; i++) - { - if (cm.getAlpha(pixels[i]) == 0) - pixels[i] = bgcolor.getRGB(); - } - } - else - for (int i = 0; i < pixels.length; i++) - pixels[i] |= 0xFF000000; + draw(new Arc2D.Double((double) x, (double) y, (double) width, + (double) height, (double) startAngle, + (double) arcAngle, Arc2D.OPEN)); + } - drawPixels(pixels, r.getWidth(), r.getHeight(), r.getWidth(), i2u); + public void drawLine(int x1, int y1, int x2, int y2) + { + draw(new Line2D.Double(x1, y1, x2, y2)); + } - updateBufferedImage(); - - // Cairo seems loosing the current color. - setColor(fg); - - return true; + public void drawRect(int x, int y, int width, int height) + { + draw(new Rectangle(x, y, width, height)); } - public void drawRenderedImage(RenderedImage image, AffineTransform xform) + public void fillArc(int x, int y, int width, int height, int startAngle, + int arcAngle) { - drawRaster(image.getColorModel(), image.getData(), xform, bg); + fill(new Arc2D.Double((double) x, (double) y, (double) width, + (double) height, (double) startAngle, + (double) arcAngle, Arc2D.OPEN)); } - public void drawRenderableImage(RenderableImage image, AffineTransform xform) + public void fillRect(int x, int y, int width, int height) { - drawRenderedImage(image.createRendering(new RenderContext(xform)), xform); + fill(new Rectangle(x, y, width, height)); } - public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) + public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { - return drawImage(img, xform, bg, obs); + fill(new Polygon(xPoints, yPoints, nPoints)); } - public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y) + public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { - Image filtered = op.filter(image, null); - drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), bg, null); + draw(new Polygon(xPoints, yPoints, nPoints)); } - public boolean drawImage(Image img, int x, int y, ImageObserver observer) + public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { - return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), bg, - observer); + draw(new Polygon(xPoints, yPoints, nPoints)); } - /////////////////////////////////////////////// - ////// Unimplemented Stubs and Overloads ////// - /////////////////////////////////////////////// + public void drawOval(int x, int y, int width, int height) + { + drawArc(x, y, width, height, 0, 360); + } - public boolean hit(Rectangle rect, Shape text, boolean onStroke) + public void drawRoundRect(int x, int y, int width, int height, int arcWidth, + int arcHeight) { - throw new java.lang.UnsupportedOperationException(); + draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); } - public GraphicsConfiguration getDeviceConfiguration() + public void fillOval(int x, int y, int width, int height) { - throw new java.lang.UnsupportedOperationException(); + fillArc(x, y, width, height, 0, 360); } - public void setComposite(Composite comp) + public void fillRoundRect(int x, int y, int width, int height, int arcWidth, + int arcHeight) { - this.comp = comp; + fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight)); + } - if (comp instanceof AlphaComposite) + /** + * CopyArea - performs clipping to the native surface as a convenience + * (requires getRealBounds). Then calls copyAreaImpl. + */ + public void copyArea(int ox, int oy, int owidth, int oheight, + int odx, int ody) + { + Point2D pos = transform.transform(new Point2D.Double(ox, oy), + (Point2D) null); + Point2D dim = transform.transform(new Point2D.Double(ox + owidth, + oy + oheight), + (Point2D) null); + Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody), + (Point2D) null); + int x = (int)pos.getX(); + int y = (int)pos.getY(); + int width = (int)(dim.getX() - pos.getX()); + int height = (int)(dim.getY() - pos.getY()); + int dx = (int)(p2.getX() - pos.getX()); + int dy = (int)(p2.getY() - pos.getY()); + + Rectangle2D r = getRealBounds(); + + if( width < 0 || height < 0 ) + return; + // Return if outside the surface + if( x + dx > r.getWidth() || y + dy > r.getHeight() ) + return; + + if( x + dx + width < r.getX() || y + dy + height < r.getY() ) + return; + + // Clip edges if necessary + if( x + dx < r.getX() ) // left { - AlphaComposite a = (AlphaComposite) comp; - cairoSetOperator(a.getRule()); - Color c = getColor(); - setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), - (int) (a.getAlpha() * ((float) c.getAlpha())))); + width = x + dx + width; + x = (int)r.getX() - dx; } - else + + if( y + dy < r.getY() ) // top { - // FIXME: implement general Composite support - throw new java.lang.UnsupportedOperationException(); + height = y + dy + height; + y = (int)r.getY() - dy; } + + if( x + dx + width >= r.getWidth() ) // right + width = (int)r.getWidth() - dx - x; + + if( y + dy + height >= r.getHeight() ) // bottom + height = (int)r.getHeight() - dy - y; + + copyAreaImpl(x, y, width, height, dx, dy); } + ///////////////////////// RENDERING HINTS /////////////////////////////////// + + /** + * FIXME- support better + */ public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) { hints.put(hintKey, hintValue); @@ -1181,23 +1010,23 @@ public class GdkGraphics2D extends Graphics2D || hintKey.equals(RenderingHints.KEY_ALPHA_INTERPOLATION)) { if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)) - cairoSurfaceSetFilter(0); + cairoSurfaceSetFilter(nativePointer, 0); else if (hintValue.equals(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) - cairoSurfaceSetFilter(1); + cairoSurfaceSetFilter(nativePointer, 1); else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED)) - cairoSurfaceSetFilter(2); + cairoSurfaceSetFilter(nativePointer, 2); else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)) - cairoSurfaceSetFilter(3); + cairoSurfaceSetFilter(nativePointer, 3); else if (hintValue.equals(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT)) - cairoSurfaceSetFilter(4); + cairoSurfaceSetFilter(nativePointer, 4); } shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE) - || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT); + || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT); } public Object getRenderingHint(RenderingHints.Key hintKey) @@ -1213,26 +1042,26 @@ public class GdkGraphics2D extends Graphics2D if (hints.containsKey(RenderingHints.KEY_INTERPOLATION)) { if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)) - cairoSurfaceSetFilter(0); + cairoSurfaceSetFilter(nativePointer, 0); else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) - cairoSurfaceSetFilter(1); + cairoSurfaceSetFilter(nativePointer, 1); } if (hints.containsKey(RenderingHints.KEY_ALPHA_INTERPOLATION)) { if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED)) - cairoSurfaceSetFilter(2); + cairoSurfaceSetFilter(nativePointer, 2); else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY)) - cairoSurfaceSetFilter(3); + cairoSurfaceSetFilter(nativePointer, 3); else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT)) - cairoSurfaceSetFilter(4); + cairoSurfaceSetFilter(nativePointer, 4); } shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE) - || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT); + || hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT); } public void addRenderingHints(Map hints) @@ -1245,31 +1074,127 @@ public class GdkGraphics2D extends Graphics2D return hints; } - public Composite getComposite() + ///////////////////////// IMAGE. METHODS /////////////////////////////////// + + protected boolean drawImage(Image img, AffineTransform xform, + Color bgcolor, ImageObserver obs) { - if (comp == null) - return AlphaComposite.SrcOver; + if (img == null) + return false; + + // In this case, xform is an AffineTransform that transforms bounding + // box of the specified image from image space to user space. However + // when we pass this transform to cairo, cairo will use this transform + // to map "user coordinates" to "pixel" coordinates, which is the + // other way around. Therefore to get the "user -> pixel" transform + // that cairo wants from "image -> user" transform that we currently + // have, we will need to invert the transformation matrix. + AffineTransform invertedXform = new AffineTransform(); + + try + { + invertedXform = xform.createInverse(); + } + catch (NoninvertibleTransformException e) + { + throw new ImagingOpException("Unable to invert transform " + + xform.toString()); + } + + // Unrecognized image - convert to a BufferedImage + if( !(img instanceof BufferedImage) ) + { + ImageProducer source = img.getSource(); + if (source == null) + return false; + img = Toolkit.getDefaultToolkit().createImage(source); + } + + BufferedImage b = (BufferedImage) img; + DataBuffer db; + double[] i2u = new double[6]; + int width = b.getWidth(); + int height = b.getHeight(); + + // If this BufferedImage has a BufferedImageGraphics object, + // use the cached CairoSurface that BIG is drawing onto + if( BufferedImageGraphics.bufferedImages.get( b ) != null ) + db = (DataBuffer)BufferedImageGraphics.bufferedImages.get( b ); else - return comp; + db = b.getRaster().getDataBuffer(); + + invertedXform.getMatrix(i2u); + + if(db instanceof CairoSurface) + { + ((CairoSurface)db).drawSurface(nativePointer, i2u); + return true; + } + + if( bgcolor != null ) + { + // Fill a rectangle with the background color + // to composite the image onto. + Paint oldPaint = paint; + AffineTransform oldTransform = transform; + setPaint( bgcolor ); + setTransform( invertedXform ); + fillRect(0, 0, width, height); + setTransform( oldTransform ); + setPaint( oldPaint ); + } + + int[] pixels; + + // Shortcut for easy color models. + if( b.getColorModel().equals(rgb32) ) + { + pixels = ((DataBufferInt)db).getData(); + for(int i = 0; i < pixels.length; i++) + pixels[i] |= 0xFF000000; + } + else if( b.getColorModel().equals(argb32) ) + { + pixels = ((DataBufferInt)db).getData(); + } + else + { + pixels = b.getRGB(0, 0, width, height, + null, 0, width); + } + + drawPixels(nativePointer, pixels, width, height, width, i2u); + + // Cairo seems to lose the current color which must be restored. + updateColor(); + return true; } - public FontRenderContext getFontRenderContext() + public void drawRenderedImage(RenderedImage image, AffineTransform xform) { - return new FontRenderContext(transform, true, true); + drawRaster(image.getColorModel(), image.getData(), xform, null); } - public void copyArea(int x, int y, int width, int height, int dx, int dy) + public void drawRenderableImage(RenderableImage image, AffineTransform xform) { - GdkGraphics2D g = (GdkGraphics2D) create(x, y, width, height); - gdkDrawDrawable(g, x + dx, y + dy); + drawRenderedImage(image.createRendering(new RenderContext(xform)), xform); } - public void drawArc(int x, int y, int width, int height, int startAngle, - int arcAngle) + public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { - draw(new Arc2D.Double((double) x, (double) y, (double) width, - (double) height, (double) startAngle, - (double) arcAngle, Arc2D.OPEN)); + return drawImage(img, xform, null, obs); + } + + public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y) + { + Image filtered = op.filter(image, null); + drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null); + } + + public boolean drawImage(Image img, int x, int y, ImageObserver observer) + { + return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, + observer); } public boolean drawImage(Image img, int x, int y, Color bgcolor, @@ -1284,6 +1209,8 @@ public class GdkGraphics2D extends Graphics2D { double scaleX = width / (double) img.getWidth(observer); double scaleY = height / (double) img.getHeight(observer); + if( scaleX == 0 || scaleY == 0 ) + return true; return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y), bgcolor, observer); @@ -1292,7 +1219,7 @@ public class GdkGraphics2D extends Graphics2D public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { - return drawImage(img, x, y, width, height, bg, observer); + return drawImage(img, x, y, width, height, null, observer); } public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, @@ -1302,109 +1229,58 @@ public class GdkGraphics2D extends Graphics2D if (img == null) return false; - Image subImage; - int sourceWidth = sx2 - sx1; int sourceHeight = sy2 - sy1; int destWidth = dx2 - dx1; int destHeight = dy2 - dy1; + if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 || + sourceHeight == 0) + return true; + double scaleX = destWidth / (double) sourceWidth; double scaleY = destHeight / (double) sourceHeight; - // Get the subimage of the source enclosed in the - // rectangle specified by sx1, sy1, sx2, sy2 - - if (img instanceof BufferedImage) - { - BufferedImage b = (BufferedImage) img; - subImage = b.getSubimage(sx1, sy1, sx2, sy2); - } + // FIXME: Avoid using an AT if possible here - it's at least twice as slow. + + Shape oldClip = getClip(); + int cx, cy, cw, ch; + if( dx1 < dx2 ) + { cx = dx1; cw = dx2 - dx1; } else - { - // FIXME: This code currently doesn't work. Null Pointer - // exception is thrown in this case. This happens - // because img.getSource() always returns null, since source gets - // never initialized when it is created with the help of - // createImage(int width, int height). - CropImageFilter filter = new CropImageFilter(sx1, sx2, sx2, sy2); - FilteredImageSource src = new FilteredImageSource(img.getSource(), - filter); - - subImage = Toolkit.getDefaultToolkit().createImage(src); - } + { cx = dx2; cw = dx1 - dx2; } + if( dy1 < dy2 ) + { cy = dy1; ch = dy2 - dy1; } + else + { cy = dy2; ch = dy1 - dy2; } + + setClip( cx, cy, cw, ch ); - return drawImage(subImage, - new AffineTransform(scaleX, 0, 0, scaleY, dx1, dy1), - bgcolor, observer); + AffineTransform tx = new AffineTransform(); + tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY ); + tx.scale( scaleX, scaleY ); + + boolean retval = drawImage(img, tx, bgcolor, observer); + setClip( oldClip ); + return retval; } public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) { - return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bg, observer); - } - - public void drawOval(int x, int y, int width, int height) - { - drawArc(x, y, width, height, 0, 360); - } - - public void drawRoundRect(int x, int y, int width, int height, int arcWidth, - int arcHeight) - { - if (arcWidth > width) - arcWidth = width; - if (arcHeight > height) - arcHeight = height; - - int xx = x + width - arcWidth; - int yy = y + height - arcHeight; - - drawArc(x, y, arcWidth, arcHeight, 90, 90); - drawArc(xx, y, arcWidth, arcHeight, 0, 90); - drawArc(xx, yy, arcWidth, arcHeight, 270, 90); - drawArc(x, yy, arcWidth, arcHeight, 180, 90); - - int y1 = y + arcHeight / 2; - int y2 = y + height - arcHeight / 2; - drawLine(x, y1, x, y2); - drawLine(x + width, y1, x + width, y2); - - int x1 = x + arcWidth / 2; - int x2 = x + width - arcWidth / 2; - drawLine(x1, y, x2, y); - drawLine(x1, y + height, x2, y + height); + return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer); } - // these are the most accelerated painting paths - native void cairoDrawGlyphVector(GdkFontPeer font, - float x, float y, int n, - int[] codes, float[] positions); - - native void cairoDrawGdkTextLayout(GdkTextLayout gl, - float x, float y); - - GdkFontPeer getFontPeer() - { - return (GdkFontPeer) getFont().getPeer(); - } - - public void drawGdkTextLayout(GdkTextLayout gl, float x, float y) - { - cairoDrawGdkTextLayout (gl, x, y); - updateBufferedImage (); - } + ///////////////////////// TEXT METHODS //////////////////////////////////// public void drawString(String str, float x, float y) { if (str == null || str.length() == 0) return; - - drawGlyphVector(getFont().createGlyphVector(null, str), x, y); - updateBufferedImage (); + (new TextLayout( str, getFont(), getFontRenderContext() )). + draw(this, x, y); } public void drawString(String str, int x, int y) @@ -1419,13 +1295,22 @@ public class GdkGraphics2D extends Graphics2D public void drawGlyphVector(GlyphVector gv, float x, float y) { - int n = gv.getNumGlyphs (); - int[] codes = gv.getGlyphCodes (0, n, null); - float[] positions = gv.getGlyphPositions (0, n, null); - - setFont (gv.getFont ()); - cairoDrawGlyphVector (getFontPeer(), x, y, n, codes, positions); - updateBufferedImage (); + if (gv instanceof FreetypeGlyphVector) + { + int n = gv.getNumGlyphs (); + int[] codes = gv.getGlyphCodes (0, n, null); + float[] positions = gv.getGlyphPositions (0, n, null); + + setFont (gv.getFont ()); + cairoDrawGlyphVector(nativePointer, (GdkFontPeer)getFont().getPeer(), + x, y, n, codes, positions); + } + else + { + translate(x, y); + fill(gv.getOutline()); + translate(-x, -y); + } } public void drawString(AttributedCharacterIterator ci, float x, float y) @@ -1434,52 +1319,19 @@ public class GdkGraphics2D extends Graphics2D drawGlyphVector(gv, x, y); } - public void fillArc(int x, int y, int width, int height, int startAngle, - int arcAngle) - { - fill(new Arc2D.Double((double) x, (double) y, (double) width, - (double) height, (double) startAngle, - (double) arcAngle, Arc2D.OPEN)); - } - - public void fillOval(int x, int y, int width, int height) - { - fillArc(x, y, width, height, 0, 360); - } - - public void fillRoundRect(int x, int y, int width, int height, int arcWidth, - int arcHeight) - { - if (arcWidth > width) - arcWidth = width; - if (arcHeight > height) - arcHeight = height; - - int xx = x + width - arcWidth; - int yy = y + height - arcHeight; - - fillArc(x, y, arcWidth, arcHeight, 90, 90); - fillArc(xx, y, arcWidth, arcHeight, 0, 90); - fillArc(xx, yy, arcWidth, arcHeight, 270, 90); - fillArc(x, yy, arcWidth, arcHeight, 180, 90); - - fillRect(x, y + arcHeight / 2, width, height - arcHeight + 1); - fillRect(x + arcWidth / 2, y, width - arcWidth + 1, height); - } - - public Font getFont() + /** + * Should perhaps be contexct dependent, but this is left for now as an + * overloadable default implementation. + */ + public FontRenderContext getFontRenderContext() { - if (font == null) - return new Font("SansSerif", Font.PLAIN, 12); - return font; + return new FontRenderContext(transform, true, true); } // Until such time as pango is happy to talk directly to cairo, we // actually need to redirect some calls from the GtkFontPeer and // GtkFontMetrics into the drawing kit and ask cairo ourselves. - static native void releasePeerGraphicsResource(GdkFontPeer f); - public FontMetrics getFontMetrics() { return getFontMetrics(getFont()); @@ -1507,6 +1359,27 @@ public class GdkGraphics2D extends Graphics2D .getFont(f.getName(), f.getAttributes()); } + public Font getFont() + { + if (font == null) + return new Font("SansSerif", Font.PLAIN, 12); + return font; + } + + /////////////////////// MISC. PUBLIC METHODS ///////////////////////////////// + + public boolean hit(Rectangle rect, Shape s, boolean onStroke) + { + if( onStroke ) + { + Shape stroked = stroke.createStrokedShape( s ); + return stroked.intersects( (double)rect.x, (double)rect.y, + (double)rect.width, (double)rect.height ); + } + return s.intersects( (double)rect.x, (double)rect.y, + (double)rect.width, (double)rect.height ); + } + public String toString() { return (getClass().getName() @@ -1514,4 +1387,219 @@ public class GdkGraphics2D extends Graphics2D + ",color=" + fg.toString() + "]"); } + + ///////////////////////// PRIVATE METHODS /////////////////////////////////// + + /** + * All the drawImage() methods eventually get delegated here if the image + * is not a Cairo surface. + * + * @param bgcolor - if non-null draws the background color before + * drawing the image. + */ + private boolean drawRaster(ColorModel cm, Raster r, + AffineTransform imageToUser, Color bgcolor) + { + if (r == null) + return false; + + SampleModel sm = r.getSampleModel(); + DataBuffer db = r.getDataBuffer(); + + if (db == null || sm == null) + return false; + + if (cm == null) + cm = ColorModel.getRGBdefault(); + + double[] i2u = new double[6]; + if (imageToUser != null) + imageToUser.getMatrix(i2u); + else + { + i2u[0] = 1; + i2u[1] = 0; + i2u[2] = 0; + i2u[3] = 1; + i2u[4] = 0; + i2u[5] = 0; + } + + int[] pixels = findSimpleIntegerArray(cm, r); + + if (pixels == null) + { + // FIXME: I don't think this code will work correctly with a non-RGB + // MultiPixelPackedSampleModel. Although this entire method should + // probably be rewritten to better utilize Cairo's different supported + // data formats. + if (sm instanceof MultiPixelPackedSampleModel) + { + pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels); + for (int i = 0; i < pixels.length; i++) + pixels[i] = cm.getRGB(pixels[i]); + } + else + { + pixels = new int[r.getWidth() * r.getHeight()]; + for (int i = 0; i < pixels.length; i++) + pixels[i] = cm.getRGB(db.getElem(i)); + } + } + + // Change all transparent pixels in the image to the specified bgcolor, + // or (if there's no alpha) fill in an alpha channel so that it paints + // correctly. + if (cm.hasAlpha()) + { + if (bgcolor != null && cm.hasAlpha()) + for (int i = 0; i < pixels.length; i++) + { + if (cm.getAlpha(pixels[i]) == 0) + pixels[i] = bgcolor.getRGB(); + } + } + else + for (int i = 0; i < pixels.length; i++) + pixels[i] |= 0xFF000000; + + drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(), + r.getWidth(), i2u); + + // Cairo seems to lose the current color which must be restored. + updateColor(); + + return true; + } + + /** + * Shifts coordinates by 0.5. + */ + private double shifted(double coord, boolean doShift) + { + if (doShift) + return Math.floor(coord) + 0.5; + else + return coord; + } + + /** + * Adds a pathIterator to the current Cairo path, also sets the cairo winding rule. + */ + private void walkPath(PathIterator p, boolean doShift) + { + double x = 0; + double y = 0; + double[] coords = new double[6]; + + cairoSetFillRule(nativePointer, p.getWindingRule()); + for (; ! p.isDone(); p.next()) + { + int seg = p.currentSegment(coords); + switch (seg) + { + case PathIterator.SEG_MOVETO: + x = shifted(coords[0], doShift); + y = shifted(coords[1], doShift); + cairoMoveTo(nativePointer, x, y); + break; + case PathIterator.SEG_LINETO: + x = shifted(coords[0], doShift); + y = shifted(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 x2 = x1 + (1.0 / 3.0) * (shifted(coords[2], doShift) - x); + double y2 = y1 + (1.0 / 3.0) * (shifted(coords[3], doShift) - y); + + x = shifted(coords[2], doShift); + y = shifted(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); + break; + case PathIterator.SEG_CLOSE: + cairoClosePath(nativePointer); + break; + } + } + } + + /** + * Used by setRenderingHints() + */ + private Map getDefaultHints() + { + HashMap defaultHints = new HashMap(); + + defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT); + + defaultHints.put(RenderingHints.KEY_STROKE_CONTROL, + RenderingHints.VALUE_STROKE_DEFAULT); + + defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS, + RenderingHints.VALUE_FRACTIONALMETRICS_OFF); + + defaultHints.put(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_OFF); + + defaultHints.put(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_DEFAULT); + + return defaultHints; + } + + /** + * Used by drawRaster and GdkPixbufDecoder + */ + public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster) + { + if (cm == null || raster == null) + return null; + + if (! cm.getColorSpace().isCS_sRGB()) + return null; + + if (! (cm instanceof DirectColorModel)) + return null; + + DirectColorModel dcm = (DirectColorModel) cm; + + if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00 + || dcm.getBlueMask() != 0x000000FF) + return null; + + if (! (raster instanceof WritableRaster)) + return null; + + if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT) + return null; + + if (! (raster.getDataBuffer() instanceof DataBufferInt)) + return null; + + DataBufferInt db = (DataBufferInt) raster.getDataBuffer(); + + if (db.getNumBanks() != 1) + return null; + + // Finally, we have determined that this is a single bank, [A]RGB-int + // buffer in sRGB space. It's worth checking all this, because it means + // that cairo can paint directly into the data buffer, which is very + // fast compared to all the normal copying and converting. + + return db.getData(); + } } diff --git a/gnu/java/awt/peer/gtk/CairoSurface.java b/gnu/java/awt/peer/gtk/CairoSurface.java new file mode 100644 index 000000000..8287b95f2 --- /dev/null +++ b/gnu/java/awt/peer/gtk/CairoSurface.java @@ -0,0 +1,315 @@ +/* CairoSurface.java + 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.peer.gtk; + +import java.awt.Graphics; +import java.awt.Color; +import java.awt.Image; +import java.awt.Point; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DirectColorModel; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; +import java.io.ByteArrayOutputStream; +import java.io.BufferedInputStream; +import java.net.URL; +import gnu.classpath.Pointer; + +/** + * CairoSurface - wraps a Cairo surface. + * + * @author Sven de Marothy + */ +public class CairoSurface extends DataBuffer +{ + int width = -1, height = -1; + + /** + * The native pointer to the Cairo surface. + */ + long surfacePointer; + + /** + * The native pointer to the image's data buffer + */ + long bufferPointer; + + + static ColorModel nativeModel = new DirectColorModel(32, + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); + + /** + * Allocates and clears the buffer and creates the cairo surface. + * @param width, height - the image size + * @param stride - the buffer row stride. + */ + private native void create(int width, int height, int stride); + + /** + * Destroys the cairo surface and frees the buffer. + */ + private native void destroy(long surfacePointer, long bufferPointer); + + /** + * Gets buffer elements + */ + private native int nativeGetElem(long bufferPointer, int i); + + /** + * Sets buffer elements. + */ + private native void nativeSetElem(long bufferPointer, int i, int val); + + /** + * Draws this image to a given CairoGraphics context, + * with an affine transform given by i2u. + */ + public native void nativeDrawSurface(long surfacePointer, long contextPointer, + double[] i2u); + + public void drawSurface(long contextPointer, double[] i2u) + { + nativeDrawSurface(surfacePointer, contextPointer, i2u); + } + + /** + * getPixels -return the pixels as a java array. + */ + native int[] nativeGetPixels(long bufferPointer, int size); + + public int[] getPixels(int size) + { + return nativeGetPixels(bufferPointer, size); + } + + /** + * getPixels -return the pixels as a java array. + */ + native void nativeSetPixels(long bufferPointer, int[] pixels); + + public void setPixels(int[] pixels) + { + nativeSetPixels(bufferPointer, pixels); + } + + native long getFlippedBuffer(long bufferPointer, int size); + + /** + * Create a cairo_surface_t with specified width and height. + * The format will be ARGB32 with premultiplied alpha and native bit + * and word ordering. + */ + CairoSurface(int width, int height) + { + super(DataBuffer.TYPE_INT, width * height); + + if(width <= 0 || height <= 0) + throw new IllegalArgumentException("Image must be at least 1x1 pixels."); + + this.width = width; + this.height = height; + + create(width, height, width * 4); + + if(surfacePointer == 0 || bufferPointer == 0) + throw new Error("Could not allocate bitmap."); + } + + /** + * Create a cairo_surface_t from a GtkImage instance. + * (data is copied, not shared) + */ + CairoSurface(GtkImage image) + { + super(DataBuffer.TYPE_INT, image.width * image.height); + + if(image.width <= 0 || image.height <= 0) + throw new IllegalArgumentException("Image must be at least 1x1 pixels."); + + width = image.width; + height = image.height; + + create(width, height, width * 4); + + if(surfacePointer == 0 || bufferPointer == 0) + throw new Error("Could not allocate bitmap."); + + // Copy the pixel data from the GtkImage. + int[] data = image.getPixels(); + + // Swap ordering from GdkPixbuf to Cairo + for(int i = 0; i < data.length; i++ ) + { + int alpha = (data[i] & 0xFF000000) >> 24; + if( alpha == 0 ) // I do not know why we need this, but it works. + data[i] = 0; + else + { + int r = (((data[i] & 0x00FF0000) >> 16) ); + int g = (((data[i] & 0x0000FF00) >> 8) ); + int b = ((data[i] & 0x000000FF) ); + data[i] = (( alpha << 24 ) & 0xFF000000) + | (( b << 16 ) & 0x00FF0000) + | (( g << 8 ) & 0x0000FF00) + | ( r & 0x000000FF); + } + } + + setPixels( data ); + } + + /** + * Dispose of the native data. + */ + public void dispose() + { + if(surfacePointer != 0) + destroy(surfacePointer, bufferPointer); + } + + /** + * Call dispose() to clean up any native resources allocated. + */ + protected void finalize() + { + dispose(); + } + + /** + * Return a GtkImage from this Cairo surface. + */ + public GtkImage getGtkImage() + { + return new GtkImage( width, height, + getFlippedBuffer(bufferPointer, width * height )); + } + + /** + * Returns a BufferedImage backed by a Cairo surface. + */ + public static BufferedImage getBufferedImage(int width, int height) + { + return getBufferedImage(new CairoSurface(width, height)); + } + + /** + * Returns a BufferedImage backed by a Cairo surface, + * created from a GtkImage. + */ + public static BufferedImage getBufferedImage(GtkImage image) + { + return getBufferedImage(new CairoSurface(image)); + } + + /** + * Returns a BufferedImage backed by a Cairo surface. + */ + public static BufferedImage getBufferedImage(CairoSurface surface) + { + WritableRaster raster = Raster.createPackedRaster + (surface, surface.width, surface.height, surface.width, + new int[]{ 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 }, + new Point(0,0)); + + return new BufferedImage(nativeModel, raster, true, new Hashtable()); + } + + /** + * DataBank.getElem implementation + */ + public int getElem(int bank, int i) + { + if(bank != 0 || i < 0 || i >= width*height) + throw new IndexOutOfBoundsException(i+" size: "+width*height); + return nativeGetElem(bufferPointer, i); + } + + /** + * DataBank.setElem implementation + */ + public void setElem(int bank, int i, int val) + { + if(bank != 0 || i < 0 || i >= width*height) + throw new IndexOutOfBoundsException(i+" size: "+width*height); + nativeSetElem(bufferPointer, i, val); + } + + /** + * Return a Graphics2D drawing to the CairoSurface. + */ + public Graphics2D getGraphics() + { + return new CairoSurfaceGraphics(this); + } + + ///// Methods used by CairoSurfaceGraphics ///// + /** + * Creates a cairo_t drawing context, returns the pointer as a long. + * Used by CairoSurfaceGraphics. + */ + native long nativeNewCairoContext(long surfacePointer); + + public long newCairoContext() + { + return nativeNewCairoContext(surfacePointer); + } + + /** + * Copy an area of the surface. Expects parameters must be within bounds. + * Count on a segfault otherwise. + */ + native void copyAreaNative2(long bufferPointer, int x, int y, int width, + int height, int dx, int dy, int stride); + public void copyAreaNative(int x, int y, int width, + int height, int dx, int dy, int stride) + { + copyAreaNative2(bufferPointer, x, y, width, height, dx, dy, stride); + } +} diff --git a/gnu/java/awt/peer/gtk/CairoSurfaceGraphics.java b/gnu/java/awt/peer/gtk/CairoSurfaceGraphics.java new file mode 100644 index 000000000..27dab66f7 --- /dev/null +++ b/gnu/java/awt/peer/gtk/CairoSurfaceGraphics.java @@ -0,0 +1,101 @@ +/* CairoSurfaceGraphics.java + 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.peer.gtk; + +import java.awt.Graphics; +import java.awt.Color; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Point; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.geom.Rectangle2D; +import java.awt.image.*; + +/** + * Implementation of Graphics2D on a Cairo surface. + */ +public class CairoSurfaceGraphics extends CairoGraphics2D +{ + protected CairoSurface surface; + private long cairo_t; + + /** + * Create a graphics context from a cairo surface + */ + public CairoSurfaceGraphics(CairoSurface surface) + { + this.surface = surface; + cairo_t = surface.newCairoContext(); + setup( cairo_t ); + setClip(0, 0, surface.width, surface.height); + } + + /** + * Creates another context from a surface. + * Used by create(). + */ + private CairoSurfaceGraphics(CairoSurfaceGraphics copyFrom) + { + surface = copyFrom.surface; + cairo_t = surface.newCairoContext(); + copy( copyFrom, cairo_t ); + setClip(0, 0, surface.width, surface.height); + } + + public Graphics create() + { + return new CairoSurfaceGraphics(this); + } + + public GraphicsConfiguration getDeviceConfiguration() + { + return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); + } + + protected Rectangle2D getRealBounds() + { + return new Rectangle2D.Double(0.0, 0.0, surface.width, surface.height); + } + + public void copyAreaImpl(int x, int y, int width, int height, int dx, int dy) + { + surface.copyAreaNative(x, y, width, height, dx, dy, surface.width); + } +} diff --git a/gnu/java/awt/peer/gtk/ComponentGraphics.java b/gnu/java/awt/peer/gtk/ComponentGraphics.java new file mode 100644 index 000000000..341e514d4 --- /dev/null +++ b/gnu/java/awt/peer/gtk/ComponentGraphics.java @@ -0,0 +1,275 @@ +/* ComponentGraphics.java -- + 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.peer.gtk; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Point; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.ImageObserver; +import java.awt.image.ImagingOpException; +import java.awt.image.RenderedImage; + +/** + * ComponentGraphics - context for drawing directly to a component, + * as this is an X drawable, it requires that we use GTK locks. + * + * This context draws directly to the drawable and requires xrender. + */ +public class ComponentGraphics extends CairoGraphics2D +{ + private GtkComponentPeer component; + protected long cairo_t; + + ComponentGraphics() + { + } + + private ComponentGraphics(GtkComponentPeer component) + { + this.component = component; + cairo_t = initState(component); + setup( cairo_t ); + Rectangle bounds = component.awtComponent.getBounds(); + setClip( new Rectangle( 0, 0, bounds.width, bounds.height) ); + setBackground(component.awtComponent.getBackground()); + setColor(component.awtComponent.getForeground()); + } + + private ComponentGraphics(ComponentGraphics cg) + { + component = cg.component; + cairo_t = initState(component); + copy( cg, cairo_t ); + Rectangle bounds = component.awtComponent.getBounds(); + setClip( new Rectangle( 0, 0, bounds.width, bounds.height) ); + setBackground(component.awtComponent.getBackground()); + setColor(component.awtComponent.getForeground()); + } + + /** + * Creates a cairo_t for the component surface and return it. + */ + private native long initState(GtkComponentPeer component); + + /** + * Destroys the component surface and calls dispose on the cairo + * graphics2d to destroy any super class resources. + */ + public void dispose() + { + super.dispose(); + disposeSurface(nativePointer); + } + + /** + * Destroys the component surface. + */ + private native void disposeSurface(long nativePointer); + + /** + * Creates a cairo_t for a volatile image + */ + protected native long initFromVolatile( long pixmapPtr, int width, int height); + + /** + * Grab lock + */ + private native void start_gdk_drawing(); + + /** + * Release lock + */ + private native void end_gdk_drawing(); + + /** + * Query if the system has the XRender extension. + */ + public static native boolean hasXRender(); + + + private native void copyAreaNative(GtkComponentPeer component, int x, int y, + int width, int height, int dx, int dy); + + private native void drawVolatile(GtkComponentPeer component, + long vimg, int x, int y, + int width, int height); + + /** + * Returns a Graphics2D object for a component, either an instance of this + * class (if xrender is supported), or a context which copies. + */ + public static Graphics2D getComponentGraphics(GtkComponentPeer component) + { + if( hasXRender() ) + return new ComponentGraphics(component); + + Rectangle r = component.awtComponent.getBounds(); + return new ComponentGraphicsCopy(r.width, r.height, component); + } + + public GraphicsConfiguration getDeviceConfiguration() + { + return component.getGraphicsConfiguration(); + } + + public Graphics create() + { + return new ComponentGraphics(this); + } + + protected Rectangle2D getRealBounds() + { + return component.awtComponent.getBounds(); + } + + public void copyAreaImpl(int x, int y, int width, int height, int dx, int dy) + { + copyAreaNative(component, x, y, width, height, dx, dy); + } + + /** + * Overloaded methods that do actual drawing need to enter the gdk threads + * and also do certain things before and after. + */ + public void draw(Shape s) + { + start_gdk_drawing(); + try + { + super.draw(s); + } + finally + { + end_gdk_drawing(); + } + } + + public void fill(Shape s) + { + start_gdk_drawing(); + try + { + super.fill(s); + } + finally + { + end_gdk_drawing(); + } + } + + public void drawRenderedImage(RenderedImage image, AffineTransform xform) + { + start_gdk_drawing(); + try + { + super.drawRenderedImage(image, xform); + } + finally + { + end_gdk_drawing(); + } + } + + protected boolean drawImage(Image img, AffineTransform xform, + Color bgcolor, ImageObserver obs) + { + boolean rv; + start_gdk_drawing(); + try + { + rv = super.drawImage(img, xform, bgcolor, obs); + } + finally + { + end_gdk_drawing(); + } + return rv; + } + + public void drawGlyphVector(GlyphVector gv, float x, float y) + { + start_gdk_drawing(); + try + { + super.drawGlyphVector(gv, x, y); + } + finally + { + end_gdk_drawing(); + } + } + + public boolean drawImage(Image img, int x, int y, ImageObserver observer) + { + if( img instanceof GtkVolatileImage ) + { + GtkVolatileImage vimg = (GtkVolatileImage) img; + drawVolatile( component, vimg.nativePointer, + x, y - 20, vimg.width, vimg.height ); + return true; + } + return super.drawImage( img, x, y, observer ); + } + + public boolean drawImage(Image img, int x, int y, int width, int height, + ImageObserver observer) + { + if( img instanceof GtkVolatileImage ) + { + GtkVolatileImage vimg = (GtkVolatileImage) img; + drawVolatile( component, vimg.nativePointer, x, y - 20, + width, height ); + return true; + } + return super.drawImage( img, x, y, width, height, observer ); + } + +} + diff --git a/gnu/java/awt/peer/gtk/ComponentGraphicsCopy.java b/gnu/java/awt/peer/gtk/ComponentGraphicsCopy.java new file mode 100644 index 000000000..286fbeac0 --- /dev/null +++ b/gnu/java/awt/peer/gtk/ComponentGraphicsCopy.java @@ -0,0 +1,129 @@ +/* ComponentGraphicsCopy.java + 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.peer.gtk; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.RenderedImage; +import java.awt.image.ImageObserver; + +/** + * Implementation of Graphics2D for Components for servers which + * do not have xrender. + * + * A mirrored GtkImage of the component is stored in memory + * and copied back. Yay. + */ +public class ComponentGraphicsCopy extends CairoSurfaceGraphics +{ + private GtkComponentPeer component; + + /** + * GtkImage sharing its data buffer with this Cairo surface. + */ + private GtkImage gtkimage; + + private int width, height; + + native void getPixbuf( GtkComponentPeer component, GtkImage image ); + + native void copyPixbuf( GtkComponentPeer component, GtkImage image, + int x, int y, int w, int h ); + + public ComponentGraphicsCopy(int width, int height, + GtkComponentPeer component) + { + super( new CairoSurface( width, height ) ); + this.component = component; + this.width = width; + this.height = height; + gtkimage = surface.getGtkImage(); + getPixbuf( component, gtkimage ); + } + + /** + * Overloaded methods that do actual drawing need to enter the gdk threads + * and also do certain things before and after. + */ + public void draw(Shape s) + { + super.draw(s); + Rectangle r = s.getBounds(); + copyPixbuf(component, gtkimage, r.x, r.y, r.width, r.height); + } + + public void fill(Shape s) + { + super.fill(s); + Rectangle r = s.getBounds(); + copyPixbuf(component, gtkimage, r.x, r.y, r.width, r.height); + } + + public void drawRenderedImage(RenderedImage image, AffineTransform xform) + { + super.drawRenderedImage(image, xform); + copyPixbuf(component, gtkimage, 0, 0, width, height); + } + + protected boolean drawImage(Image img, AffineTransform xform, + Color bgcolor, ImageObserver obs) + { + boolean rv = super.drawImage(img, xform, bgcolor, obs); + copyPixbuf(component, gtkimage, 0, 0, width, height); + return rv; + } + + public void drawGlyphVector(GlyphVector gv, float x, float y) + { + super.drawGlyphVector(gv, x, y); + Rectangle r = gv.getPixelBounds(getFontRenderContext(), x , y); + copyPixbuf(component, gtkimage, r.x, r.y, r.width, r.height); + } +} + diff --git a/gnu/java/awt/peer/gtk/FreetypeGlyphVector.java b/gnu/java/awt/peer/gtk/FreetypeGlyphVector.java new file mode 100644 index 000000000..4978c6a45 --- /dev/null +++ b/gnu/java/awt/peer/gtk/FreetypeGlyphVector.java @@ -0,0 +1,468 @@ +/* FreetypeGlyphVector.java + 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.peer.gtk; + +import java.awt.Font; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.GeneralPath; +import java.awt.font.GlyphJustificationInfo; +import java.awt.font.GlyphMetrics; +import java.awt.font.GlyphVector; +import java.awt.font.FontRenderContext; + +public class FreetypeGlyphVector extends GlyphVector +{ + /** + * The associated font and its peer. + */ + private Font font; + private GdkFontPeer peer; // ATTN: Accessed from native code. + + private Rectangle2D logicalBounds; + + private float[] glyphPositions; + /** + * The string represented by this GlyphVector. + */ + private String s; + + /** + * The font render context + */ + private FontRenderContext frc; + + /** + * The total # of glyphs. + */ + private int nGlyphs; + + /** + * The glyph codes + */ + private int[] glyphCodes; + + /** + * Glyph transforms. (de facto only the translation is used) + */ + private AffineTransform[] glyphTransforms; + + private GlyphMetrics[] metricsCache; + + /** + * Create a glyphvector from a given (Freetype) font and a String. + */ + public FreetypeGlyphVector(Font f, String s, FontRenderContext frc) + { + this(f, s, frc, Font.LAYOUT_LEFT_TO_RIGHT); + } + + /** + * Create a glyphvector from a given (Freetype) font and a String. + */ + public FreetypeGlyphVector(Font f, String s, FontRenderContext frc, + int flags) + { + this.s = s; + this.font = f; + this.frc = frc; + if( !(font.getPeer() instanceof GdkFontPeer ) ) + throw new IllegalArgumentException("Not a valid font."); + peer = (GdkFontPeer)font.getPeer(); + + getGlyphs(); + if( flags == Font.LAYOUT_RIGHT_TO_LEFT ) + { + // reverse the glyph ordering. + int[] temp = new int[ nGlyphs ]; + for(int i = 0; i < nGlyphs; i++) + temp[ i ] = glyphCodes[ nGlyphs - i - 1]; + glyphCodes = temp; + } + performDefaultLayout(); + } + + /** + * Create a glyphvector from a given set of glyph codes. + */ + public FreetypeGlyphVector(Font f, int[] codes, FontRenderContext frc) + { + this.font = f; + this.frc = frc; + if( !(font.getPeer() instanceof GdkFontPeer ) ) + throw new IllegalArgumentException("Not a valid font."); + peer = (GdkFontPeer)font.getPeer(); + + glyphCodes = new int[ codes.length ]; + System.arraycopy(codes, 0, glyphCodes, 0, codes.length); + nGlyphs = glyphCodes.length; + performDefaultLayout(); + } + + /** + * Create the array of glyph codes. + */ + private void getGlyphs() + { + nGlyphs = s.codePointCount( 0, s.length() ); + glyphCodes = new int[ nGlyphs ]; + int[] codePoints = new int[ nGlyphs ]; + int stringIndex = 0; + + for(int i = 0; i < nGlyphs; i++) + { + codePoints[i] = s.codePointAt( stringIndex ); + // UTF32 surrogate handling + if( codePoints[i] != (int)s.charAt( stringIndex ) ) + stringIndex ++; + stringIndex ++; + } + + glyphCodes = getGlyphs( codePoints ); + } + + /** + * Returns the glyph code within the font for a given character + */ + public native int[] getGlyphs(int[] codepoints); + + /** + * Returns the kerning of a glyph pair + */ + private native Point2D getKerning(int leftGlyph, int rightGlyph); + + private native double[] getMetricsNative( int glyphCode ); + + private native GeneralPath getGlyphOutlineNative(int glyphIndex); + + /** + * Duh, compares two instances. + */ + public boolean equals(GlyphVector gv) + { + if( ! (gv instanceof FreetypeGlyphVector) ) + return false; + + return (((FreetypeGlyphVector)gv).font.equals(font) && + ((FreetypeGlyphVector)gv).frc.equals(frc) + && ((FreetypeGlyphVector)gv).s.equals(s)); + } + + /** + * Returns the associated Font + */ + public Font getFont() + { + return font; + } + + /** + * Returns the associated FontRenderContext + */ + public FontRenderContext getFontRenderContext() + { + return frc; + } + + /** + * Layout the glyphs. + */ + public void performDefaultLayout() + { + logicalBounds = null; // invalidate caches. + glyphPositions = null; + + glyphTransforms = new AffineTransform[ nGlyphs ]; + double x = 0; + + for(int i = 0; i < nGlyphs; i++) + { + GlyphMetrics gm = getGlyphMetrics( i ); + glyphTransforms[ i ] = AffineTransform.getTranslateInstance(x, 0); + x += gm.getAdvanceX(); + if( i > 0 ) + { + Point2D p = getKerning( glyphCodes[ i - 1 ], glyphCodes[ i ] ); + x += p.getX(); + } + } + } + + /** + * Returns the code of the glyph at glyphIndex; + */ + public int getGlyphCode(int glyphIndex) + { + return glyphCodes[ glyphIndex ]; + } + + /** + * Returns multiple glyphcodes. + */ + public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, + int[] codeReturn) + { + int[] rval; + + if( codeReturn == null ) + rval = new int[ numEntries ]; + else + rval = codeReturn; + + System.arraycopy(glyphCodes, beginGlyphIndex, rval, 0, numEntries); + + return rval; + } + + /** + * FIXME: Implement me. + */ + public Shape getGlyphLogicalBounds(int glyphIndex) + { + GlyphMetrics gm = getGlyphMetrics( glyphIndex ); + if( gm == null ) + return null; + Rectangle2D r = gm.getBounds2D(); + return new Rectangle2D.Double( r.getX() - gm.getLSB(), r.getY(), + gm.getAdvanceX(), r.getHeight() ); + } + + /* + * FIXME: Not all glyph types are supported. + * (The JDK doesn't really seem to do so either) + */ + public void setupGlyphMetrics() + { + metricsCache = new GlyphMetrics[ nGlyphs ]; + + for(int i = 0; i < nGlyphs; i++) + { + GlyphMetrics gm = (GlyphMetrics) + peer.getGlyphMetrics( glyphCodes[ i ] ); + if( gm == null ) + { + double[] val = getMetricsNative( glyphCodes[ i ] ); + if( val == null ) + gm = null; + else + { + gm = new GlyphMetrics( true, + (float)val[1], + (float)val[2], + new Rectangle2D.Double + ( val[3], val[4], + val[5], val[6] ), + GlyphMetrics.STANDARD ); + peer.putGlyphMetrics( glyphCodes[ i ], gm ); + } + } + metricsCache[ i ] = gm; + } + } + + /** + * Returns the metrics of a single glyph. + */ + public GlyphMetrics getGlyphMetrics(int glyphIndex) + { + if( metricsCache == null ) + setupGlyphMetrics(); + + return metricsCache[ glyphIndex ]; + } + + /** + * Returns the outline of a single glyph. + */ + public Shape getGlyphOutline(int glyphIndex) + { + GeneralPath gp = getGlyphOutlineNative( glyphCodes[ glyphIndex ] ); + gp.transform( glyphTransforms[ glyphIndex ] ); + return gp; + } + + /** + * Returns the position of a single glyph. + */ + public Point2D getGlyphPosition(int glyphIndex) + { + return glyphTransforms[ glyphIndex ].transform( new Point2D.Double(0, 0), + null ); + } + + /** + * Returns the positions of multiple glyphs. + */ + public float[] getGlyphPositions(int beginGlyphIndex, int numEntries, + float[] positionReturn) + { + if( glyphPositions != null ) + return glyphPositions; + + float[] rval; + + if( positionReturn == null ) + rval = new float[2 * numEntries]; + else + rval = positionReturn; + + for( int i = beginGlyphIndex; i < numEntries; i++ ) + { + Point2D p = getGlyphPosition( i ); + rval[i * 2] = (float)p.getX(); + rval[i * 2 + 1] = (float)p.getY(); + } + + glyphPositions = rval; + return rval; + } + + /** + * Returns the transform of a glyph. + */ + public AffineTransform getGlyphTransform(int glyphIndex) + { + return new AffineTransform( glyphTransforms[ glyphIndex ] ); + } + + /** + * Returns the visual bounds of a glyph + * May be off by a pixel or two due to hinting/rasterization. + */ + public Shape getGlyphVisualBounds(int glyphIndex) + { + return getGlyphOutline( glyphIndex ).getBounds2D(); + } + + /** + * Return the logical bounds of the whole thing. + */ + public Rectangle2D getLogicalBounds() + { + if( nGlyphs == 0 ) + return new Rectangle2D.Double(0, 0, 0, 0); + if( logicalBounds != null ) + return logicalBounds; + + Rectangle2D rect = (Rectangle2D)getGlyphLogicalBounds( 0 ); + for( int i = 1; i < nGlyphs; i++ ) + { + Rectangle2D r2 = (Rectangle2D)getGlyphLogicalBounds( i ); + Point2D p = getGlyphPosition( i ); + r2.setRect( p.getX(), p.getY(), r2.getWidth(), r2.getHeight() ); + rect = rect.createUnion( r2 ); + } + + logicalBounds = rect; + return rect; + } + + /** + * Returns the number of glyphs. + */ + public int getNumGlyphs() + { + return glyphCodes.length; + } + + /** + * Returns the outline of the entire GlyphVector. + */ + public Shape getOutline() + { + GeneralPath path = new GeneralPath(); + for( int i = 0; i < getNumGlyphs(); i++ ) + path.append( getGlyphOutline( i ), false ); + return path; + } + + /** + * TODO: + * FreeType does not currently have an API for the JSTF table. We should + * probably get the table ourselves from FT and pass it to some parser + * which the native font peers will need. + */ + public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) + { + return null; + } + + /** + * Returns the outline of the entire vector, drawn at (x,y). + */ + public Shape getOutline(float x, float y) + { + AffineTransform tx = AffineTransform.getTranslateInstance( x, y ); + GeneralPath gp = (GeneralPath)getOutline(); + gp.transform( tx ); + return gp; + } + + /** + * Returns the visual bounds of the entire GlyphVector. + * May be off by a pixel or two due to hinting/rasterization. + */ + public Rectangle2D getVisualBounds() + { + return getOutline().getBounds2D(); + } + + /** + * Sets the position of a glyph. + */ + public void setGlyphPosition(int glyphIndex, Point2D newPos) + { + // FIXME: Scaling, etc.? + glyphTransforms[ glyphIndex ].setToTranslation( newPos.getX(), + newPos.getY() ); + logicalBounds = null; + glyphPositions = null; + } + + /** + * Sets the transform of a single glyph. + */ + public void setGlyphTransform(int glyphIndex, AffineTransform newTX) + { + glyphTransforms[ glyphIndex ].setTransform( newTX ); + logicalBounds = null; + glyphPositions = null; + } +} diff --git a/gnu/java/awt/peer/gtk/GdkFontPeer.java b/gnu/java/awt/peer/gtk/GdkFontPeer.java index 544efa3c9..f5ed8a710 100644 --- a/gnu/java/awt/peer/gtk/GdkFontPeer.java +++ b/gnu/java/awt/peer/gtk/GdkFontPeer.java @@ -47,27 +47,32 @@ import java.awt.FontMetrics; import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; +import java.awt.font.GlyphMetrics; import java.awt.font.LineMetrics; import java.awt.geom.Rectangle2D; +import java.awt.geom.Point2D; import java.text.CharacterIterator; import java.text.StringCharacterIterator; 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 native void initStaticState(); private final int native_state = GtkGenericPeer.getUniqueInteger (); private static ResourceBundle bundle; + + /** + * Cache GlyphMetrics objects. + */ + private HashMap metricsCache; static { - if (Configuration.INIT_LOAD_LIBRARY) - { - System.loadLibrary("gtkpeer"); - } + System.loadLibrary("gtkpeer"); initStaticState (); @@ -90,9 +95,12 @@ public class GdkFontPeer extends ClasspathFontPeer native void getFontMetrics(double [] metrics); native void getTextMetrics(String str, double [] metrics); + native void releasePeerGraphicsResource(); + + protected void finalize () { - GdkGraphics2D.releasePeerGraphicsResource(this); + releasePeerGraphicsResource(); dispose (); } @@ -143,6 +151,7 @@ public class GdkFontPeer extends ClasspathFontPeer super(name, style, size); initState (); setFont (this.familyName, this.style, (int)this.size); + metricsCache = new HashMap(); } public GdkFontPeer (String name, Map attributes) @@ -150,6 +159,7 @@ public class GdkFontPeer extends ClasspathFontPeer super(name, attributes); initState (); setFont (this.familyName, this.style, (int)this.size); + metricsCache = new HashMap(); } /** @@ -234,39 +244,41 @@ public class GdkFontPeer extends ClasspathFontPeer return -1; } - private native GdkGlyphVector getGlyphVector(String txt, - Font f, - FontRenderContext ctx); - public GlyphVector createGlyphVector (Font font, FontRenderContext ctx, CharacterIterator i) { - return getGlyphVector(buildString (i), font, ctx); + return new FreetypeGlyphVector(font, buildString (i), ctx); } public GlyphVector createGlyphVector (Font font, FontRenderContext ctx, int[] glyphCodes) { - return null; - // return new GdkGlyphVector (font, this, ctx, glyphCodes); + return new FreetypeGlyphVector(font, glyphCodes, ctx); } public byte getBaselineFor (Font font, char c) { - throw new UnsupportedOperationException (); + // FIXME: Actually check. + return Font.ROMAN_BASELINE; } - protected class GdkFontLineMetrics extends LineMetrics + private static class GdkFontLineMetrics extends LineMetrics { - FontMetrics fm; - int nchars; + private FontMetrics fm; + private int nchars; + private float strikethroughOffset, strikethroughThickness, + underlineOffset, underlineThickness; - public GdkFontLineMetrics (FontMetrics m, int n) + public GdkFontLineMetrics (GdkFontPeer fp, FontMetrics m, int n) { fm = m; nchars = n; + strikethroughOffset = 0f; + underlineOffset = 0f; + strikethroughThickness = ((float)fp.getSize(null)) / 12f; + underlineThickness = strikethroughThickness; } public float getAscent() @@ -275,7 +287,8 @@ public class GdkFontPeer extends ClasspathFontPeer } public int getBaselineIndex() - { + { + // FIXME return Font.ROMAN_BASELINE; } @@ -306,7 +319,7 @@ public class GdkFontPeer extends ClasspathFontPeer public LineMetrics getLineMetrics (Font font, CharacterIterator ci, int begin, int limit, FontRenderContext rc) { - return new GdkFontLineMetrics (getFontMetrics (font), limit - begin); + return new GdkFontLineMetrics (this, getFontMetrics (font), limit - begin); } public Rectangle2D getMaxCharBounds (Font font, FontRenderContext rc) @@ -338,7 +351,9 @@ public class GdkFontPeer extends ClasspathFontPeer public Rectangle2D getStringBounds (Font font, CharacterIterator ci, int begin, int limit, FontRenderContext frc) { - GdkGlyphVector gv = getGlyphVector(buildString (ci, begin, limit), font, frc); + GlyphVector gv = new FreetypeGlyphVector( font, + buildString(ci, begin, limit), + frc); return gv.getVisualBounds(); } @@ -351,20 +366,15 @@ public class GdkFontPeer extends ClasspathFontPeer char[] chars, int start, int limit, int flags) { - int nchars = (limit - start) + 1; - char[] nc = new char[nchars]; - - for (int i = 0; i < nchars; ++i) - nc[i] = chars[start + i]; - - return createGlyphVector (font, frc, - new StringCharacterIterator (new String (nc))); + return new FreetypeGlyphVector( font, new String( chars, start, + limit - start), + frc, flags); } public LineMetrics getLineMetrics (Font font, String str, FontRenderContext frc) { - return new GdkFontLineMetrics (getFontMetrics (font), str.length ()); + return new GdkFontLineMetrics (this, getFontMetrics (font), str.length ()); } public FontMetrics getFontMetrics (Font font) @@ -374,4 +384,20 @@ public class GdkFontPeer extends ClasspathFontPeer return Toolkit.getDefaultToolkit().getFontMetrics (font); } + /** + * Returns a cached GlyphMetrics object for a given glyphcode, + * or null if it doesn't exist in the cache. + */ + GlyphMetrics getGlyphMetrics( int glyphCode ) + { + return (GlyphMetrics)metricsCache.get( new Integer( glyphCode ) ); + } + + /** + * Put a GlyphMetrics object in the cache. + */ + void putGlyphMetrics( int glyphCode, Object metrics ) + { + metricsCache.put( new Integer( glyphCode ), metrics ); + } } diff --git a/gnu/java/awt/peer/gtk/GdkGlyphVector.java b/gnu/java/awt/peer/gtk/GdkGlyphVector.java deleted file mode 100644 index f0ddea43a..000000000 --- a/gnu/java/awt/peer/gtk/GdkGlyphVector.java +++ /dev/null @@ -1,359 +0,0 @@ -/* GdkGlyphVector.java -- Glyph vector object - Copyright (C) 2003, 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 java.awt.Font; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.font.FontRenderContext; -import java.awt.font.GlyphJustificationInfo; -import java.awt.font.GlyphMetrics; -import java.awt.font.GlyphVector; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; - -public class GdkGlyphVector extends GlyphVector -{ - - /* We use a simple representation for glyph vectors here. Glyph i - * consumes 8 doubles: - * - * logical x: extents[ 10*i ] - * logical y: extents[ 10*i + 1 ] - * logical width: extents[ 10*i + 2 ] - * logical height: extents[ 10*i + 3 ] - * - * visual x: extents[ 10*i + 4 ] - * visual y: extents[ 10*i + 5 ] - * visual width: extents[ 10*i + 6 ] - * visual height: extents[ 10*i + 7 ] - * - * origin pos x: extents[ 10*i + 8 ] - * origin pos y: extents[ 10*i + 9 ] - * - * as well as one int, code[i], representing the glyph code in the font. - */ - - double [] extents; - int [] codes; - - Font font; - FontRenderContext fontRenderContext; - - Rectangle2D allLogical; - Rectangle2D allVisual; - - public GdkGlyphVector(double[] extents, int[] codes, Font font, FontRenderContext frc) - { - this.extents = extents; - this.codes = codes; - this.font = font; - this.fontRenderContext = frc; - - allLogical = new Rectangle2D.Double(); - allVisual = new Rectangle2D.Double(); - - for (int i = 0; i < codes.length; ++i) - { - allLogical.add (new Rectangle2D.Double(extents[10*i ] + extents[10*i + 8], - extents[10*i + 1] + extents[10*i + 9], - extents[10*i + 2], - extents[10*i + 3])); - - allVisual.add (new Rectangle2D.Double(extents[10*i + 4] + extents[10*i + 8], - extents[10*i + 5] + extents[10*i + 9], - extents[10*i + 6], - extents[10*i + 7])); - } - } - - /* - geometric notes: - - the FRC contains a mapping from points -> pixels. - - typographics points are typically 1/72 of an inch. - - pixel displays are often around 72 dpi. - - so the FRC can get away with using an identity transform on a screen, - often. behavior is documented by sun to fall back to an identity - transform if the internal transformation is null. - - coordinates coming up from pango are expressed as floats -- in device - space, so basically pixels-with-fractional-bits -- derived from their - storage format in pango (1024ths of pixels). - - it is not clear from the javadocs whether the results of methods like - getGlyphPositions ought to return coordinates in device space, or - "point" space, or what. for now I'm returning them in device space. - - */ - - public double[] getExtents() - { - return extents; - } - - public int[] getCodes() - { - return codes; - } - - public Font getFont () - { - return font; - } - - public FontRenderContext getFontRenderContext () - { - return fontRenderContext; - } - - public int getGlyphCharIndex (int glyphIndex) - { - // FIXME: currently pango does not provide glyph-by-glyph - // reverse mapping information, so we assume a broken 1:1 - // glyph model here. This is plainly wrong. - return glyphIndex; - } - - public int[] getGlyphCharIndices (int beginGlyphIndex, - int numEntries, - int[] codeReturn) - { - int ix[] = codeReturn; - if (ix == null) - ix = new int[numEntries]; - - for (int i = 0; i < numEntries; i++) - ix[i] = getGlyphCharIndex (beginGlyphIndex + i); - return ix; - } - - public int getGlyphCode (int glyphIndex) - { - return codes[glyphIndex]; - } - - public int[] getGlyphCodes (int beginGlyphIndex, int numEntries, - int[] codeReturn) - { - if (codeReturn == null) - codeReturn = new int[numEntries]; - - System.arraycopy(codes, beginGlyphIndex, codeReturn, 0, numEntries); - return codeReturn; - } - - public Shape getGlyphLogicalBounds (int i) - { - return new Rectangle2D.Double (extents[8*i], extents[8*i + 1], - extents[8*i + 2], extents[8*i + 3]); - } - - public GlyphMetrics getGlyphMetrics (int i) - { - // FIXME: pango does not yield vertical layout information at the - // moment. - - boolean is_horizontal = true; - double advanceX = extents[8*i + 2]; // "logical width" == advanceX - double advanceY = 0; - - return new GlyphMetrics (is_horizontal, - (float) advanceX, (float) advanceY, - (Rectangle2D) getGlyphVisualBounds(i), - GlyphMetrics.STANDARD); - } - - public Shape getGlyphOutline (int glyphIndex) - { - throw new UnsupportedOperationException (); - } - - public Shape getGlyphOutline (int glyphIndex, float x, float y) - { - throw new UnsupportedOperationException (); - } - - public Rectangle getGlyphPixelBounds (int i, - FontRenderContext renderFRC, - float x, float y) - { - return new Rectangle((int) x, (int) y, - (int) extents[8*i + 6], (int) extents[8*i + 7]); - } - - public Point2D getGlyphPosition (int i) - { - return new Point2D.Double (extents[10*i + 8], - extents[10*i + 9]); - } - - public float[] getGlyphPositions (int beginGlyphIndex, - int numEntries, - float[] positionReturn) - { - float fx[] = positionReturn; - if (fx == null) - fx = new float[numEntries * 2]; - - for (int i = 0; i < numEntries; ++i) - { - fx[2*i ] = (float) extents[10*i + 8]; - fx[2*i + 1] = (float) extents[10*i + 9]; - } - return fx; - } - - public AffineTransform getGlyphTransform (int glyphIndex) - { - // Glyphs don't have independent transforms in these simple glyph - // vectors; docs specify null is an ok return here. - return null; - } - - public Shape getGlyphVisualBounds (int i) - { - return new Rectangle2D.Double(extents[8*i + 4], extents[8*i + 5], - extents[8*i + 6], extents[8*i + 7]); - } - - public int getLayoutFlags () - { - return 0; - } - - public Rectangle2D getLogicalBounds () - { - return allLogical; - } - - public int getNumGlyphs () - { - return codes.length; - } - - public Shape getOutline () - { - throw new UnsupportedOperationException (); - } - - public Rectangle getPixelBounds (FontRenderContext renderFRC, - float x, float y) - { - return new Rectangle((int)x, - (int)y, - (int) allVisual.getWidth(), - (int) allVisual.getHeight()); - } - - public Rectangle2D getVisualBounds () - { - return allVisual; - } - - public void performDefaultLayout () - { - } - - public void setGlyphPosition (int i, Point2D newPos) - { - extents[8*i ] = newPos.getX(); - extents[8*i + 1] = newPos.getY(); - - extents[8*i + 4] = newPos.getX(); - extents[8*i + 5] = newPos.getY(); - } - - public void setGlyphTransform (int glyphIndex, - AffineTransform newTX) - { - // not yet.. maybe not ever? - throw new UnsupportedOperationException (); - } - - public boolean equals(GlyphVector gv) - { - if (gv == null) - return false; - - if (! (gv instanceof GdkGlyphVector)) - return false; - - GdkGlyphVector ggv = (GdkGlyphVector) gv; - - if ((ggv.codes.length != this.codes.length) - || (ggv.extents.length != this.extents.length)) - return false; - - if ((ggv.font == null && this.font != null) - || (ggv.font != null && this.font == null) - || (!ggv.font.equals(this.font))) - return false; - - if ((ggv.fontRenderContext == null && this.fontRenderContext != null) - || (ggv.fontRenderContext != null && this.fontRenderContext == null) - || (!ggv.fontRenderContext.equals(this.fontRenderContext))) - return false; - - for (int i = 0; i < ggv.codes.length; ++i) - if (ggv.codes[i] != this.codes[i]) - return false; - - for (int i = 0; i < ggv.extents.length; ++i) - if (ggv.extents[i] != this.extents[i]) - return false; - - return true; - } - - public GlyphJustificationInfo getGlyphJustificationInfo(int idx) - { - throw new UnsupportedOperationException (); - } - - public Shape getOutline(float x, float y) - { - throw new UnsupportedOperationException (); - } - -} diff --git a/gnu/java/awt/peer/gtk/GdkGraphicsConfiguration.java b/gnu/java/awt/peer/gtk/GdkGraphicsConfiguration.java index 6cf7310a5..147f8f3e6 100644 --- a/gnu/java/awt/peer/gtk/GdkGraphicsConfiguration.java +++ b/gnu/java/awt/peer/gtk/GdkGraphicsConfiguration.java @@ -1,5 +1,5 @@ /* GdkGraphicsConfiguration.java -- describes characteristics of graphics - Copyright (C) 2000, 2001, 2002, 2003, 2004 Free Software Foundation + Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006 Free Software Foundation This file is part of GNU Classpath. @@ -42,26 +42,33 @@ import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.ImageCapabilities; import java.awt.Rectangle; -import java.awt.Toolkit; +import java.awt.Transparency; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; +import java.awt.image.DirectColorModel; import java.awt.image.VolatileImage; public class GdkGraphicsConfiguration extends GraphicsConfiguration { GdkScreenGraphicsDevice gdkScreenGraphicsDevice; - ColorModel cm; - Rectangle bounds; + + ColorModel opaqueColorModel; + ColorModel bitmaskColorModel; + + ColorModel translucentColorModel; + public GdkGraphicsConfiguration(GdkScreenGraphicsDevice dev) { - this.gdkScreenGraphicsDevice = dev; - cm = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getColorModel(); - bounds = ((GtkToolkit) Toolkit.getDefaultToolkit()).getBounds(); + gdkScreenGraphicsDevice = dev; + + opaqueColorModel = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, 0); + bitmaskColorModel = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, 0x1000000); + translucentColorModel = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF, 0xFF000000); } public GraphicsDevice getDevice() @@ -94,12 +101,21 @@ public class GdkGraphicsConfiguration public ColorModel getColorModel() { - return cm; + return opaqueColorModel; } public ColorModel getColorModel(int transparency) { - return getColorModel(); + switch (transparency) + { + case Transparency.OPAQUE: + return opaqueColorModel; + case Transparency.BITMASK: + return bitmaskColorModel; + default: + case Transparency.TRANSLUCENT: + return translucentColorModel; + } } public AffineTransform getDefaultTransform() @@ -116,7 +132,7 @@ public class GdkGraphicsConfiguration public Rectangle getBounds() { - return bounds; + return gdkScreenGraphicsDevice.getBounds(); } public BufferCapabilities getBufferCapabilities() @@ -133,8 +149,8 @@ public class GdkGraphicsConfiguration public VolatileImage createCompatibleVolatileImage(int width, int height, int transparency) { - // FIXME: implement - return null; + // FIXME: support the transparency argument + return new GtkVolatileImage(width, height); } } diff --git a/gnu/java/awt/peer/gtk/GdkGraphicsEnvironment.java b/gnu/java/awt/peer/gtk/GdkGraphicsEnvironment.java index 4b0b5d308..035819d1c 100644 --- a/gnu/java/awt/peer/gtk/GdkGraphicsEnvironment.java +++ b/gnu/java/awt/peer/gtk/GdkGraphicsEnvironment.java @@ -1,5 +1,5 @@ /* GdkGraphicsEnvironment.java -- information about the graphics environment - Copyright (C) 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -43,33 +43,71 @@ import java.awt.Graphics2D; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; -import java.awt.Toolkit; import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; import java.util.Locale; public class GdkGraphicsEnvironment extends GraphicsEnvironment { + private final int native_state = GtkGenericPeer.getUniqueInteger (); + + private GdkScreenGraphicsDevice defaultDevice; + + private GdkScreenGraphicsDevice[] devices; + + static + { + System.loadLibrary("gtkpeer"); + + initStaticState (); + } + + static native void initStaticState(); + public GdkGraphicsEnvironment () { + nativeInitState(); } + + native void nativeInitState(); public GraphicsDevice[] getScreenDevices () { - // FIXME: Support multiple screens, since GDK can. - return new GraphicsDevice[] { new GdkScreenGraphicsDevice (this) }; + if (devices == null) + { + devices = nativeGetScreenDevices(); + } + + return (GraphicsDevice[]) devices.clone(); } + + private native GdkScreenGraphicsDevice[] nativeGetScreenDevices(); public GraphicsDevice getDefaultScreenDevice () { if (GraphicsEnvironment.isHeadless ()) throw new HeadlessException (); - - return new GdkScreenGraphicsDevice (this); + + synchronized (GdkGraphicsEnvironment.class) + { + if (defaultDevice == null) + { + defaultDevice = nativeGetDefaultScreenDevice(); + } + } + + return defaultDevice; } + + private native GdkScreenGraphicsDevice nativeGetDefaultScreenDevice(); public Graphics2D createGraphics (BufferedImage image) { - return new GdkGraphics2D (image); + DataBuffer db = image.getRaster().getDataBuffer(); + if(db instanceof CairoSurface) + return ((CairoSurface)db).getGraphics(); + + return new BufferedImageGraphics( image ); } private native int nativeGetNumFontFamilies(); @@ -80,20 +118,21 @@ public class GdkGraphicsEnvironment extends GraphicsEnvironment throw new java.lang.UnsupportedOperationException (); } - public String[] getAvailableFontFamilyNames () - { - String[] family_names; - int array_size; + public String[] getAvailableFontFamilyNames () + { + String[] family_names; + int array_size; - array_size = nativeGetNumFontFamilies(); - family_names = new String[array_size]; + array_size = nativeGetNumFontFamilies(); + family_names = new String[array_size]; - nativeGetFontFamilies(family_names); - return family_names; - } + nativeGetFontFamilies(family_names); + return family_names; + } public String[] getAvailableFontFamilyNames (Locale l) { throw new java.lang.UnsupportedOperationException (); } + } diff --git a/gnu/java/awt/peer/gtk/GdkPixbufDecoder.java b/gnu/java/awt/peer/gtk/GdkPixbufDecoder.java index 72908ff5c..4e6181f0e 100644 --- a/gnu/java/awt/peer/gtk/GdkPixbufDecoder.java +++ b/gnu/java/awt/peer/gtk/GdkPixbufDecoder.java @@ -75,10 +75,8 @@ public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder { static { - if (Configuration.INIT_LOAD_LIBRARY) - { - System.loadLibrary("gtkpeer"); - } + System.loadLibrary("gtkpeer"); + initStaticState (); } @@ -504,19 +502,19 @@ public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder int width = ras.getWidth(); int height = ras.getHeight(); ColorModel model = image.getColorModel(); - int[] pixels = GdkGraphics2D.findSimpleIntegerArray (image.getColorModel(), ras); + int[] pixels = CairoGraphics2D.findSimpleIntegerArray (image.getColorModel(), ras); if (pixels == null) { - BufferedImage img = new BufferedImage(width, height, - (model != null && model.hasAlpha() ? - BufferedImage.TYPE_INT_ARGB - : BufferedImage.TYPE_INT_RGB)); + BufferedImage img; + if(model != null && model.hasAlpha()) + img = CairoSurface.getBufferedImage(width, height); + img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); int[] pix = new int[4]; for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) img.setRGB(x, y, model.getRGB(ras.getPixel(x, y, pix))); - pixels = GdkGraphics2D.findSimpleIntegerArray (img.getColorModel(), + pixels = CairoGraphics2D.findSimpleIntegerArray (img.getColorModel(), img.getRaster()); model = img.getColorModel(); } @@ -586,9 +584,10 @@ public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder if (bufferedImage == null) { - bufferedImage = new BufferedImage (width, height, (model != null && model.hasAlpha() ? - BufferedImage.TYPE_INT_ARGB - : BufferedImage.TYPE_INT_RGB)); + if(model != null && model.hasAlpha()) + bufferedImage = new BufferedImage (width, height, BufferedImage.TYPE_INT_ARGB); + else + bufferedImage = new BufferedImage (width, height, BufferedImage.TYPE_INT_RGB); } int pixels2[]; @@ -682,43 +681,4 @@ public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder return getBufferedImage (); } } - - // remaining helper class and static method is a convenience for the Gtk - // peers, for loading a BufferedImage in off a disk file without going - // through the whole imageio system. - - public static BufferedImage createBufferedImage (String filename) - { - GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(), - "png", // reader auto-detects, doesn't matter - new GdkPixbufDecoder (filename)); - return r.getBufferedImage (); - } - - public static BufferedImage createBufferedImage (URL u) - { - GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(), - "png", // reader auto-detects, doesn't matter - new GdkPixbufDecoder (u)); - return r.getBufferedImage (); - } - - public static BufferedImage createBufferedImage (byte[] imagedata, int imageoffset, - int imagelength) - { - GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(), - "png", // reader auto-detects, doesn't matter - new GdkPixbufDecoder (imagedata, - imageoffset, - imagelength)); - return r.getBufferedImage (); - } - - public static BufferedImage createBufferedImage (ImageProducer producer) - { - GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(), "png" /* ignored */, null); - producer.startProduction(r); - return r.getBufferedImage (); - } - } diff --git a/gnu/java/awt/peer/gtk/GdkScreenGraphicsDevice.java b/gnu/java/awt/peer/gtk/GdkScreenGraphicsDevice.java index b5d1237a4..62116a322 100644 --- a/gnu/java/awt/peer/gtk/GdkScreenGraphicsDevice.java +++ b/gnu/java/awt/peer/gtk/GdkScreenGraphicsDevice.java @@ -1,5 +1,5 @@ /* GdkScreenGraphicsDevice.java -- information about a screen device - Copyright (C) 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,44 +38,110 @@ exception statement from your version. */ package gnu.java.awt.peer.gtk; -import java.awt.Dimension; import java.awt.DisplayMode; +import java.awt.Frame; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; -import java.awt.Toolkit; +import java.awt.Rectangle; +import java.awt.Window; +import java.util.ArrayList; -public class GdkScreenGraphicsDevice extends GraphicsDevice +class GdkScreenGraphicsDevice extends GraphicsDevice { + private final int native_state = GtkGenericPeer.getUniqueInteger (); + + private Window fullscreenWindow; + + private boolean oldWindowDecorationState; + + private Rectangle oldWindowBounds; + + private Rectangle bounds; + + private GdkGraphicsConfiguration[] configurations; + + /** The <code>GdkGraphicsEnvironment</code> instance that created this + * <code>GdkScreenGraphicsDevice</code>. This is only needed for native + * methods which need to access the 'native_state' field storing a pointer + * to a GdkDisplay object. + */ GdkGraphicsEnvironment env; + + /** An identifier that is created by Gdk + */ + String idString; + + /** The display modes supported by this <code>GdkScreenGraphicsDevice</code>. + * If the array is <code>null</code> <code>nativeGetDisplayModes</code> has + * to be called. + */ + X11DisplayMode[] displayModes; - public GdkScreenGraphicsDevice (GdkGraphicsEnvironment e) - { - super (); + /** The non-changeable display mode of this <code>GdkScreenGraphicsDevice + * </code>. This field gets initialized by the {@link #init()} method. If it + * is still <code>null</code> afterwards, the XRandR extension is available + * and display mode changes are possible. If it is non-null XRandR is not + * available, no display mode changes are possible and no other native + * method must be called. + */ + DisplayMode fixedDisplayMode; + + static + { + System.loadLibrary("gtkpeer"); + + initStaticState (); + } + + static native void initStaticState(); + + GdkScreenGraphicsDevice (GdkGraphicsEnvironment e) + { + super(); env = e; + + configurations = new GdkGraphicsConfiguration[1]; + configurations[0] = new GdkGraphicsConfiguration(this); } + /** This method is called from the native side immediately after + * the constructor is run. + */ + void init() + { + fixedDisplayMode = nativeGetFixedDisplayMode(env); + } + + /** Depending on the availability of the XRandR extension the method returns + * the screens' non-changeable display mode or null, meaning that XRandR can + * handle display mode changes. + */ + native DisplayMode nativeGetFixedDisplayMode(GdkGraphicsEnvironment env); + public int getType () { + // Gdk manages only raster screens. return GraphicsDevice.TYPE_RASTER_SCREEN; } public String getIDstring () { - // FIXME: query X for this string - return "default GDK device ID string"; + if (idString == null) + idString = nativeGetIDString(); + + return idString; } + + private native String nativeGetIDString(); public GraphicsConfiguration[] getConfigurations () { - // FIXME: query X for the list of possible configurations - return new GraphicsConfiguration [] { new GdkGraphicsConfiguration(this) }; + return (GraphicsConfiguration[]) configurations.clone(); } - + public GraphicsConfiguration getDefaultConfiguration () { - - // FIXME: query X for default configuration - return new GdkGraphicsConfiguration(this); + return configurations[0]; } @@ -89,23 +155,193 @@ public class GdkScreenGraphicsDevice extends GraphicsDevice */ public DisplayMode getDisplayMode() { - // determine display mode - Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); - DisplayMode mode = new DisplayMode(dim.width, dim.height, 0, - DisplayMode.REFRESH_RATE_UNKNOWN); - return mode; + if (fixedDisplayMode != null) + return fixedDisplayMode; + + synchronized (this) + { + if (displayModes == null) + displayModes = nativeGetDisplayModes(env); + } + + int index = nativeGetDisplayModeIndex(env); + int rate = nativeGetDisplayModeRate(env); + + return new DisplayMode(displayModes[index].width, + displayModes[index].height, + DisplayMode.BIT_DEPTH_MULTI, + rate); + } + + native int nativeGetDisplayModeIndex(GdkGraphicsEnvironment env); + + native int nativeGetDisplayModeRate(GdkGraphicsEnvironment env); + + public DisplayMode[] getDisplayModes() + { + if (fixedDisplayMode != null) + return new DisplayMode[] { fixedDisplayMode }; + + synchronized (this) + { + if (displayModes == null) + displayModes = nativeGetDisplayModes(env); + } + + ArrayList list = new ArrayList(); + for(int i=0;i<displayModes.length;i++) + for(int j=0;j<displayModes[i].rates.length;j++) + list.add(new DisplayMode(displayModes[i].width, + displayModes[i].height, + DisplayMode.BIT_DEPTH_MULTI, + displayModes[i].rates[j])); + + return (DisplayMode[]) list.toArray(new DisplayMode[list.size()]); } + + native X11DisplayMode[] nativeGetDisplayModes(GdkGraphicsEnvironment env); /** - * This device does not yet support fullscreen exclusive mode, so this - * returns <code>false</code>. + * Real fullscreen exclusive mode is not supported. * * @return <code>false</code> * @since 1.4 */ public boolean isFullScreenSupported() { - return false; + return true; + } + + public boolean isDisplayChangeSupported() + { + return fixedDisplayMode == null; + } + + public void setDisplayMode(DisplayMode dm) + { + if (fixedDisplayMode != null) + throw new UnsupportedOperationException("Cannnot change display mode."); + + if (dm == null) + throw new IllegalArgumentException("DisplayMode must not be null."); + + synchronized (this) + { + if (displayModes == null) + displayModes = nativeGetDisplayModes(env); + } + + for (int i=0; i<displayModes.length; i++) + if (displayModes[i].width == dm.getWidth() + && displayModes[i].height == dm.getHeight()) + { + synchronized (this) + { + nativeSetDisplayMode(env, + i, + (short) dm.getRefreshRate()); + + bounds = null; + } + + return; + } + + throw new IllegalArgumentException("Mode not supported by this device."); + } + + native void nativeSetDisplayMode(GdkGraphicsEnvironment env, + int index, short rate); + + /** A class that simply encapsulates the X11 display mode data. + */ + static class X11DisplayMode + { + short[] rates; + int width; + int height; + + X11DisplayMode(int width, int height, short[] rates) + { + this.width = width; + this.height = height; + this.rates = rates; + } + + } + + public void setFullScreenWindow(Window w) + { + // Bring old fullscreen window back into its original state. + if (fullscreenWindow != null && w != fullscreenWindow) + { + if (fullscreenWindow instanceof Frame) + { + // Decoration state can only be switched when the peer is + // non-existent. That means we have to dispose the + // Frame. + Frame f = (Frame) fullscreenWindow; + if (oldWindowDecorationState != f.isUndecorated()) + { + f.dispose(); + f.setUndecorated(oldWindowDecorationState); + } + } + + fullscreenWindow.setBounds(oldWindowBounds); + + if (!fullscreenWindow.isVisible()) + fullscreenWindow.setVisible(true); + } + + // If applicable remove decoration, then maximize the window and + // bring it to the foreground. + if (w != null) + { + if (w instanceof Frame) + { + Frame f = (Frame) w; + oldWindowDecorationState = f.isUndecorated(); + if (!oldWindowDecorationState) + { + f.dispose(); + f.setUndecorated(true); + } + } + + oldWindowBounds = w.getBounds(); + + DisplayMode dm = getDisplayMode(); + + w.setBounds(0, 0, dm.getWidth(), dm.getHeight()); + + if (!w.isVisible()) + w.setVisible(true); + + w.requestFocus(); + w.toFront(); + + } + + fullscreenWindow = w; + } + + public Window getFullScreenWindow() + { + return fullscreenWindow; + } + + Rectangle getBounds() + { + synchronized(this) + { + if (bounds == null) + bounds = nativeGetBounds(); + } + + return bounds; } + + native Rectangle nativeGetBounds(); } diff --git a/gnu/java/awt/peer/gtk/GdkTextLayout.java b/gnu/java/awt/peer/gtk/GdkTextLayout.java index 9189bd4b7..a8765222e 100644 --- a/gnu/java/awt/peer/gtk/GdkTextLayout.java +++ b/gnu/java/awt/peer/gtk/GdkTextLayout.java @@ -1,5 +1,5 @@ /* GdkTextLayout.java - Copyright (C) 2003, 2005 Free Software Foundation, Inc. + Copyright (C) 2003, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -70,20 +70,26 @@ public class GdkTextLayout // native side, plumbing, etc. static { - if (Configuration.INIT_LOAD_LIBRARY) - { - System.loadLibrary("gtkpeer"); - } + System.loadLibrary("gtkpeer"); + initStaticState (); } private native void setText(String str); + private native void setFont(GdkFontPeer font); private native void getExtents(double[] inkExtents, double[] logExtents); private native void indexToPos(int idx, double[] pos); + private native void initState (); + private native void dispose (); + + private native void cairoDrawGdkTextLayout(long cg2d, float x, float y); + static native void initStaticState(); + private final int native_state = GtkGenericPeer.getUniqueInteger (); + protected void finalize () { dispose (); @@ -99,6 +105,15 @@ public class GdkTextLayout initState(); attributedString = str; fontRenderContext = frc; + AttributedCharacterIterator aci = str.getIterator(); + char[] chars = new char[aci.getEndIndex() - aci.getBeginIndex()]; + for(int i = aci.getBeginIndex(); i < aci.getEndIndex(); i++) + chars[i] = aci.setIndex(i); + setText(new String(chars)); + + Object fnt = aci.getAttribute(TextAttribute.FONT); + if (fnt != null && fnt instanceof Font) + setFont( (GdkFontPeer) ((Font)fnt).getPeer() ); } protected class CharacterIteratorProxy @@ -201,9 +216,7 @@ public class GdkTextLayout public void draw (Graphics2D g2, float x, float y) { - // we share pango structures directly with GdkGraphics2D - GdkGraphics2D gg2 = (GdkGraphics2D) g2; - gg2.drawGdkTextLayout(this, x, y); + cairoDrawGdkTextLayout(((CairoGraphics2D) g2).nativePointer, x, y); } public TextHitInfo getStrongCaret (TextHitInfo hit1, diff --git a/gnu/java/awt/peer/gtk/GtkComponentPeer.java b/gnu/java/awt/peer/gtk/GtkComponentPeer.java index 3e464ab1a..625855f01 100644 --- a/gnu/java/awt/peer/gtk/GtkComponentPeer.java +++ b/gnu/java/awt/peer/gtk/GtkComponentPeer.java @@ -207,13 +207,7 @@ public class GtkComponentPeer extends GtkGenericPeer public Image createImage (int width, int height) { - Image image; - image = new BufferedImage (width, height, BufferedImage.TYPE_INT_RGB); - - Graphics g = image.getGraphics(); - g.setColor(getBackground()); - g.fillRect(0, 0, width, height); - return image; + return CairoSurface.getBufferedImage(width, height); } public void disable () @@ -240,7 +234,7 @@ public class GtkComponentPeer extends GtkGenericPeer // never return null. public Graphics getGraphics () { - return new GdkGraphics2D (this); + return ComponentGraphics.getComponentGraphics(this); } public Point getLocationOnScreen () @@ -703,7 +697,7 @@ public class GtkComponentPeer extends GtkGenericPeer // on which this component is displayed. public VolatileImage createVolatileImage (int width, int height) { - return new GtkVolatileImage (width, height); + return new GtkVolatileImage (this, width, height, null); } // Creates buffers used in a buffering strategy. @@ -713,7 +707,7 @@ public class GtkComponentPeer extends GtkGenericPeer // numBuffers == 2 implies double-buffering, meaning one back // buffer and one front buffer. if (numBuffers == 2) - backBuffer = new GtkVolatileImage(awtComponent.getWidth(), + backBuffer = new GtkVolatileImage(this, awtComponent.getWidth(), awtComponent.getHeight(), caps.getBackBufferCapabilities()); else diff --git a/gnu/java/awt/peer/gtk/GtkImage.java b/gnu/java/awt/peer/gtk/GtkImage.java index 83ce2cbef..ef96518a1 100644 --- a/gnu/java/awt/peer/gtk/GtkImage.java +++ b/gnu/java/awt/peer/gtk/GtkImage.java @@ -57,14 +57,7 @@ import java.net.URL; import gnu.classpath.Pointer; /** - * GtkImage - wraps a GdkPixbuf or GdkPixmap. - * - * The constructor GtkImage(int, int) creates an 'off-screen' GdkPixmap, - * this can be drawn to (it's a GdkDrawable), and correspondingly, you can - * create a GdkGraphics object for it. - * - * This corresponds to the Image implementation returned by - * Component.createImage(int, int). + * GtkImage - wraps a GdkPixbuf. * * A GdkPixbuf is 'on-screen' and the gdk cannot draw to it, * this is used for the other constructors (and other createImage methods), and @@ -88,9 +81,10 @@ public class GtkImage extends Image boolean isLoaded; /** - * Pointer to the GdkPixbuf + * Pointer to the GdkPixbuf - + * don't change the name without changing the native code. */ - Pointer pixmap; + Pointer pixbuf; /** * Observer queue. @@ -98,11 +92,6 @@ public class GtkImage extends Image Vector observers; /** - * If offScreen is set, a GdkBitmap is wrapped and not a Pixbuf. - */ - boolean offScreen; - - /** * Error flag for loading. */ boolean errorLoading; @@ -122,71 +111,64 @@ public class GtkImage extends Image 0xFF000000); /** + * The singleton GtkImage that is returned on errors by GtkToolkit. + */ + private static GtkImage errorImage; + + /** + * Lock that should be held for all gdkpixbuf operations. We don't use + * the global gdk_threads_enter/leave functions in most places since + * most gdkpixbuf operations can be done in parallel to drawing and + * manipulating gtk widgets. + */ + static Object pixbufLock = new Object(); + + /** + * Allocate a PixBuf from a given ARGB32 buffer pointer. + */ + private native void initFromBuffer( long bufferPointer ); + + /** * Returns a copy of the pixel data as a java array. - * Should be called with the GdkPixbufDecoder.pixbufLock held. + * Should be called with the pixbufLock held. */ - private native int[] getPixels(); + native int[] getPixels(); /** * Sets the pixel data from a java array. - * Should be called with the GdkPixbufDecoder.pixbufLock held. + * Should be called with the pixbufLock held. */ private native void setPixels(int[] pixels); /** * Loads an image using gdk-pixbuf from a file. - * Should be called with the GdkPixbufDecoder.pixbufLock held. + * Should be called with the pixbufLock held. */ private native boolean loadPixbuf(String name); /** * Loads an image using gdk-pixbuf from data. - * Should be called with the GdkPixbufDecoder.pixbufLock held. + * Should be called with the pixbufLock held. */ private native boolean loadImageFromData(byte[] data); /** - * Allocates a Gtk Pixbuf or pixmap - * Should be called with the GdkPixbufDecoder.pixbufLock held. + * Allocates a Gtk Pixbuf + * Should be called with the pixbufLock held. */ - private native void createPixmap(); + private native void createPixbuf(); /** * Frees the above. - * Should be called with the GdkPixbufDecoder.pixbufLock held. - */ - private native void freePixmap(); - - /** - * Sets the pixmap to scaled copy of src image. hints are rendering hints. - * Should be called with the GdkPixbufDecoder.pixbufLock held. - */ - private native void createScaledPixmap(GtkImage src, int hints); - - /** - * Draws the image, optionally scaled and composited. - * Should be called with the GdkPixbufDecoder.pixbufLock held. - * Also acquires global gdk lock for drawing. + * Should be called with the pixbufLock held. */ - private native void drawPixelsScaled (GdkGraphics2D gc, - int bg_red, int bg_green, int bg_blue, - int x, int y, int width, int height, - boolean composite); + private native void freePixbuf(); /** - * Draws the image, optionally scaled flipped and composited. - * Should be called with the GdkPixbufDecoder.pixbufLock held. - * Also acquires global gdk lock for drawing. + * Sets the pixbuf to scaled copy of src image. hints are rendering hints. + * Should be called with the pixbufLock held. */ - private native void drawPixelsScaledFlipped (GdkGraphics2D gc, - int bg_red, int bg_green, - int bg_blue, - boolean flipX, boolean flipY, - int srcX, int srcY, - int srcWidth, int srcHeight, - int dstX, int dstY, - int dstWidth, int dstHeight, - boolean composite); + private native void createScaledPixbuf(GtkImage src, int hints); /** * Constructs a GtkImage from an ImageProducer. Asynchronity is handled in @@ -202,7 +184,6 @@ public class GtkImage extends Image source = producer; errorLoading = false; source.startProduction(new GtkImageConsumer(this, source)); - offScreen = false; } /** @@ -215,7 +196,6 @@ public class GtkImage extends Image { isLoaded = true; observers = null; - offScreen = false; props = new Hashtable(); errorLoading = false; } @@ -231,7 +211,7 @@ public class GtkImage extends Image try { String path = f.getCanonicalPath(); - synchronized(GdkPixbufDecoder.pixbufLock) + synchronized(pixbufLock) { if (loadPixbuf(f.getCanonicalPath()) != true) throw new IllegalArgumentException("Couldn't load image: " @@ -249,7 +229,6 @@ public class GtkImage extends Image isLoaded = true; observers = null; - offScreen = false; props = new Hashtable(); } @@ -261,7 +240,7 @@ public class GtkImage extends Image */ public GtkImage (byte[] data) { - synchronized(GdkPixbufDecoder.pixbufLock) + synchronized(pixbufLock) { if (loadImageFromData (data) != true) throw new IllegalArgumentException ("Couldn't load image."); @@ -269,7 +248,6 @@ public class GtkImage extends Image isLoaded = true; observers = null; - offScreen = false; props = new Hashtable(); errorLoading = false; } @@ -301,7 +279,7 @@ public class GtkImage extends Image throw new IllegalArgumentException ("Couldn't load image."); } byte[] array = baos.toByteArray(); - synchronized(GdkPixbufDecoder.pixbufLock) + synchronized(pixbufLock) { if (loadImageFromData(array) != true) throw new IllegalArgumentException ("Couldn't load image."); @@ -313,23 +291,6 @@ public class GtkImage extends Image } /** - * Constructs an empty GtkImage. - */ - public GtkImage (int width, int height) - { - this.width = width; - this.height = height; - props = new Hashtable(); - isLoaded = true; - observers = null; - offScreen = true; - synchronized(GdkPixbufDecoder.pixbufLock) - { - createPixmap(); - } - } - - /** * Constructs a scaled version of the src bitmap, using the GDK. */ private GtkImage (GtkImage src, int width, int height, int hints) @@ -339,12 +300,11 @@ public class GtkImage extends Image props = new Hashtable(); isLoaded = true; observers = null; - offScreen = false; // Use the GDK scaling method. - synchronized(GdkPixbufDecoder.pixbufLock) + synchronized(pixbufLock) { - createScaledPixmap(src, hints); + createScaledPixbuf(src, hints); } } @@ -354,19 +314,30 @@ public class GtkImage extends Image */ GtkImage (Pointer pixbuf) { - pixmap = pixbuf; - synchronized(GdkPixbufDecoder.pixbufLock) + this.pixbuf = pixbuf; + synchronized(pixbufLock) { createFromPixbuf(); } isLoaded = true; observers = null; - offScreen = false; props = new Hashtable(); } - // The singleton GtkImage that is returned on errors by GtkToolkit. - private static GtkImage errorImage; + /** + * Wraps a buffer with a GtkImage. + * + * @param bufferPointer a pointer to an ARGB32 buffer + */ + GtkImage(int width, int height, long bufferPointer) + { + this.width = width; + this.height = height; + props = new Hashtable(); + isLoaded = true; + observers = null; + initFromBuffer( bufferPointer ); + } /** * Returns an empty GtkImage with the errorLoading flag set. @@ -385,7 +356,7 @@ public class GtkImage extends Image /** * Native helper function for constructor that takes a pixbuf Pointer. - * Should be called with the GdkPixbufDecoder.pixbufLock held. + * Should be called with the pixbufLock held. */ private native void createFromPixbuf(); @@ -407,9 +378,9 @@ public class GtkImage extends Image isLoaded = true; deliver(); - synchronized(GdkPixbufDecoder.pixbufLock) + synchronized(pixbufLock) { - createPixmap(); + createPixbuf(); setPixels(pixels); } } @@ -450,30 +421,28 @@ public class GtkImage extends Image return null; int[] pixels; - synchronized(GdkPixbufDecoder.pixbufLock) + synchronized (pixbufLock) { - pixels = getPixels(); + if (!errorLoading) + pixels = getPixels(); + else + return null; } return new MemoryImageSource(width, height, nativeModel, pixels, 0, width); } /** - * Creates a GdkGraphics context for this pixmap. + * Does nothing. Should not be called. */ public Graphics getGraphics () { - if (!isLoaded) - return null; - if (offScreen) - return null; // FIXME: (Graphics) new GdkGraphics(this); - else - throw new IllegalAccessError("This method only works for off-screen" - +" Images."); + throw new IllegalAccessError("This method only works for off-screen" + +" Images."); } /** - * Returns a scaled instance of this pixmap. + * Returns a scaled instance of this pixbuf. */ public Image getScaledInstance(int width, int height, @@ -500,9 +469,9 @@ public class GtkImage extends Image { observers = new Vector(); isLoaded = false; - synchronized(GdkPixbufDecoder.pixbufLock) + synchronized(pixbufLock) { - freePixmap(); + freePixbuf(); } source.startProduction(new GtkImageConsumer(this, source)); } @@ -512,9 +481,9 @@ public class GtkImage extends Image { if (isLoaded) { - synchronized(GdkPixbufDecoder.pixbufLock) + synchronized(pixbufLock) { - freePixmap(); + freePixbuf(); } } } @@ -535,104 +504,6 @@ public class GtkImage extends Image return ImageObserver.ALLBITS | ImageObserver.WIDTH | ImageObserver.HEIGHT; } - // Drawing methods //////////////////////////////////////////////// - - /** - * Draws an image with eventual scaling/transforming. - */ - public boolean drawImage (GdkGraphics2D g, int dx1, int dy1, int dx2, int dy2, - int sx1, int sy1, int sx2, int sy2, - Color bgcolor, ImageObserver observer) - { - if (addObserver(observer)) - return false; - - boolean flipX = (dx1 > dx2)^(sx1 > sx2); - boolean flipY = (dy1 > dy2)^(sy1 > sy2); - int dstWidth = Math.abs (dx2 - dx1); - int dstHeight = Math.abs (dy2 - dy1); - int srcWidth = Math.abs (sx2 - sx1); - int srcHeight = Math.abs (sy2 - sy1); - int srcX = (sx1 < sx2) ? sx1 : sx2; - int srcY = (sy1 < sy2) ? sy1 : sy2; - int dstX = (dx1 < dx2) ? dx1 : dx2; - int dstY = (dy1 < dy2) ? dy1 : dy2; - - // Clipping. This requires the dst to be scaled as well, - if (srcWidth > width) - { - dstWidth = (int)((double)dstWidth*((double)width/(double)srcWidth)); - srcWidth = width - srcX; - } - - if (srcHeight > height) - { - dstHeight = (int)((double)dstHeight*((double)height/(double)srcHeight)); - srcHeight = height - srcY; - } - - if (srcWidth + srcX > width) - { - dstWidth = (int)((double)dstWidth * (double)(width - srcX)/(double)srcWidth); - srcWidth = width - srcX; - } - - if (srcHeight + srcY > height) - { - dstHeight = (int)((double)dstHeight * (double)(width - srcY)/(double)srcHeight); - srcHeight = height - srcY; - } - - if ( this.width <= 0 || this.height <= 0 ) - return true; - - if ( srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) - return true; - - synchronized(GdkPixbufDecoder.pixbufLock) - { - if(bgcolor != null) - drawPixelsScaledFlipped (g, bgcolor.getRed (), bgcolor.getGreen (), - bgcolor.getBlue (), - flipX, flipY, - srcX, srcY, - srcWidth, srcHeight, - dstX, dstY, - dstWidth, dstHeight, - true); - else - drawPixelsScaledFlipped (g, 0, 0, 0, flipX, flipY, - srcX, srcY, srcWidth, srcHeight, - dstX, dstY, dstWidth, dstHeight, - false); - } - return true; - } - - /** - * Draws an image to the GdkGraphics context, at (x,y) scaled to - * width and height, with optional compositing with a background color. - */ - public boolean drawImage (GdkGraphics2D g, int x, int y, int width, int height, - Color bgcolor, ImageObserver observer) - { - if (addObserver(observer)) - return false; - - if ( this.width <= 0 || this.height <= 0 ) - return true; - - synchronized(GdkPixbufDecoder.pixbufLock) - { - if(bgcolor != null) - drawPixelsScaled(g, bgcolor.getRed (), bgcolor.getGreen (), - bgcolor.getBlue (), x, y, width, height, true); - else - drawPixelsScaled(g, 0, 0, 0, x, y, width, height, false); - } - - return true; - } // Private methods //////////////////////////////////////////////// diff --git a/gnu/java/awt/peer/gtk/GtkToolkit.java b/gnu/java/awt/peer/gtk/GtkToolkit.java index 8e342cb92..163fc52f7 100644 --- a/gnu/java/awt/peer/gtk/GtkToolkit.java +++ b/gnu/java/awt/peer/gtk/GtkToolkit.java @@ -88,8 +88,7 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit static { - if (Configuration.INIT_LOAD_LIBRARY) - System.loadLibrary("gtkpeer"); + System.loadLibrary("gtkpeer"); int portableNativeSync; String portNatSyncProp = @@ -160,7 +159,7 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit Image image; try { - image = GdkPixbufDecoder.createBufferedImage(filename); + image = CairoSurface.getBufferedImage( new GtkImage( filename ) ); } catch (IllegalArgumentException iae) { @@ -174,8 +173,8 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit Image image; try { - image = GdkPixbufDecoder.createBufferedImage(url); - } + image = CairoSurface.getBufferedImage( new GtkImage( url ) ); + } catch (IllegalArgumentException iae) { image = null; @@ -185,10 +184,13 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit public Image createImage (ImageProducer producer) { + if (producer == null) + return null; + Image image; try { - image = GdkPixbufDecoder.createBufferedImage(producer); + image = CairoSurface.getBufferedImage( new GtkImage( producer ) ); } catch (IllegalArgumentException iae) { @@ -203,9 +205,9 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit Image image; try { - image = GdkPixbufDecoder.createBufferedImage(imagedata, - imageoffset, - imagelength); + byte[] data = new byte[ imagelength ]; + System.arraycopy(imagedata, imageoffset, data, 0, imagelength); + image = CairoSurface.getBufferedImage( new GtkImage( data ) ); } catch (IllegalArgumentException iae) { @@ -222,7 +224,7 @@ public class GtkToolkit extends gnu.java.awt.ClasspathToolkit */ public ImageProducer createImageProducer(URL url) { - return new GdkPixbufDecoder(url); + return createImage( url ).getSource(); } /** diff --git a/gnu/java/awt/peer/gtk/GtkVolatileImage.java b/gnu/java/awt/peer/gtk/GtkVolatileImage.java index 496090a09..f38007f19 100644 --- a/gnu/java/awt/peer/gtk/GtkVolatileImage.java +++ b/gnu/java/awt/peer/gtk/GtkVolatileImage.java @@ -1,4 +1,4 @@ -/* GtkVolatileImage.java -- a hardware-accelerated image buffer +/* GtkVolatileImage.java -- wraps an X pixmap Copyright (C) 2005 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.awt.peer.gtk; import java.awt.ImageCapabilities; +import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.image.BufferedImage; @@ -46,44 +47,82 @@ import java.awt.image.VolatileImage; public class GtkVolatileImage extends VolatileImage { - private int width; - private int height; + int width, height; private ImageCapabilities caps; - public GtkVolatileImage(int width, int height) + /** + * Don't touch, accessed from native code. + */ + long nativePointer; + + native long init(GtkComponentPeer component, int width, int height); + + native void destroy(long pointer); + + native int[] nativeGetPixels(long pointer); + public int[] getPixels() { - this(width, height, null); + return nativeGetPixels(nativePointer); } - public GtkVolatileImage(int width, int height, ImageCapabilities caps) + native void nativeCopyArea(long pointer, int x, int y, int w, int h, int dx, + int dy ); + public void copyArea(int x, int y, int w, int h, int dx, int dy) + { + nativeCopyArea(nativePointer, x, y, w, h, dx, dy); + } + + native void nativeDrawVolatile(long pointer, long srcPtr, int x, int y, + int w, int h ); + public void drawVolatile(long srcPtr, int x, int y, int w, int h ) + { + nativeDrawVolatile(nativePointer, srcPtr, x, y, w, h); + } + + public GtkVolatileImage(GtkComponentPeer component, + int width, int height, ImageCapabilities caps) { this.width = width; this.height = height; this.caps = caps; + nativePointer = init( component, width, height ); } - // FIXME: should return a buffered image snapshot of the accelerated - // visual - public BufferedImage getSnapshot() + public GtkVolatileImage(int width, int height, ImageCapabilities caps) { - return null; + this(null, width, height, caps); } - public int getWidth() + public GtkVolatileImage(int width, int height) { - return width; + this(null, width, height, null); } - public int getHeight() + public void finalize() { - return height; + dispose(); + } + + public void dispose() + { + destroy(nativePointer); + } + + public BufferedImage getSnapshot() + { + CairoSurface cs = new CairoSurface( width, height ); + cs.setPixels( getPixels() ); + return CairoSurface.getBufferedImage( cs ); + } + + public Graphics getGraphics() + { + return createGraphics(); } - // FIXME: should return a graphics wrapper around this image's - // visual public Graphics2D createGraphics() { - return null; + return new VolatileImageGraphics( this ); } public int validate(GraphicsConfiguration gc) @@ -101,18 +140,28 @@ public class GtkVolatileImage extends VolatileImage return caps; } - public synchronized Object getProperty (String name, ImageObserver observer) + public int getWidth() { - return null; + return width; + } + + public int getHeight() + { + return height; } - public synchronized int getWidth (ImageObserver observer) + public int getWidth(java.awt.image.ImageObserver observer) { return width; } - public synchronized int getHeight (ImageObserver observer) + public int getHeight(java.awt.image.ImageObserver observer) { return height; } + + public Object getProperty(String name, ImageObserver observer) + { + return null; + } } diff --git a/gnu/java/awt/peer/gtk/VolatileImageGraphics.java b/gnu/java/awt/peer/gtk/VolatileImageGraphics.java new file mode 100644 index 000000000..f45c0e0b9 --- /dev/null +++ b/gnu/java/awt/peer/gtk/VolatileImageGraphics.java @@ -0,0 +1,127 @@ +/* VolatileImageGraphics.java + 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.peer.gtk; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.GlyphVector; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferInt; +import java.awt.image.ColorModel; +import java.awt.image.DirectColorModel; +import java.awt.image.RenderedImage; +import java.awt.image.ImageObserver; +import java.util.WeakHashMap; + +public class VolatileImageGraphics extends ComponentGraphics +{ + private GtkVolatileImage owner; + + public VolatileImageGraphics(GtkVolatileImage img) + { + this.owner = img; + cairo_t = initFromVolatile( owner.nativePointer, img.width, img.height ); + setup( cairo_t ); + setClip( new Rectangle( 0, 0, img.width, img.height) ); + } + + private VolatileImageGraphics(VolatileImageGraphics copy) + { + this.owner = copy.owner; + cairo_t = initFromVolatile(owner.nativePointer, owner.width, owner.height); + copy( copy, cairo_t ); + clipRect(0, 0, owner.width, owner.height); + } + + public void copyAreaImpl(int x, int y, int width, int height, int dx, int dy) + { + owner.copyArea(x, y, width, height, dx, dy); + } + + public GraphicsConfiguration getDeviceConfiguration() + { + return null; + } + + public Graphics create() + { + return new VolatileImageGraphics( this ); + } + + + public boolean drawImage(Image img, int x, int y, ImageObserver observer) + { + if( img instanceof GtkVolatileImage ) + { + owner.drawVolatile( ((GtkVolatileImage)img).nativePointer, + x, y, + ((GtkVolatileImage)img).width, + ((GtkVolatileImage)img).height ); + return true; + } + return super.drawImage( img, x, y, observer ); + } + + public boolean drawImage(Image img, int x, int y, int width, int height, + ImageObserver observer) + { + if( img instanceof GtkVolatileImage ) + { + owner.drawVolatile( ((GtkVolatileImage)img).nativePointer, + x, y, width, height ); + return true; + } + return super.drawImage( img, x, y, width, height, observer ); + } + + protected Rectangle2D getRealBounds() + { + return new Rectangle2D.Double(0, 0, owner.width, owner.height); + } +} + diff --git a/gnu/java/awt/peer/qt/QtToolkit.java b/gnu/java/awt/peer/qt/QtToolkit.java index 591b52803..73652f8df 100644 --- a/gnu/java/awt/peer/qt/QtToolkit.java +++ b/gnu/java/awt/peer/qt/QtToolkit.java @@ -136,8 +136,7 @@ public class QtToolkit extends ClasspathToolkit { eventQueue = new EventQueue(); repaintThread = new QtRepaintThread(); - if (Configuration.INIT_LOAD_LIBRARY) - System.loadLibrary("qtpeer"); + System.loadLibrary("qtpeer"); String theme = null; try diff --git a/gnu/java/awt/print/JavaPrinterGraphics.java b/gnu/java/awt/print/JavaPrinterGraphics.java index af31309ed..9a3db0125 100644 --- a/gnu/java/awt/print/JavaPrinterGraphics.java +++ b/gnu/java/awt/print/JavaPrinterGraphics.java @@ -37,7 +37,7 @@ exception statement from your version. */ package gnu.java.awt.print; -import gnu.java.awt.peer.gtk.GtkImage; +import gnu.java.awt.peer.gtk.CairoSurface; import java.awt.Color; import java.awt.Font; @@ -185,7 +185,7 @@ public class JavaPrinterGraphics extends Graphics implements PrinterGraphics // FIXME: This should at least be BufferedImage. // Fix once we have a working B.I. // Graphics2D should also be supported of course. - image = new GtkImage(xSize, ySize); + image = CairoSurface.getBufferedImage(xSize, ySize); g = image.getGraphics(); setColor(Color.white); diff --git a/gnu/java/io/PlatformHelper.java b/gnu/java/io/PlatformHelper.java index 79ce6e8f4..e54c56da9 100644 --- a/gnu/java/io/PlatformHelper.java +++ b/gnu/java/io/PlatformHelper.java @@ -1,5 +1,5 @@ /* PlatformHelper.java -- Isolate OS-specific IO helper methods and variables - Copyright (C) 1998, 2002 Free Software Foundation, Inc. + Copyright (C) 1998, 2002, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -97,98 +97,6 @@ public class PlatformHelper } /** - * This routine canonicalizes input param "path" to formal path representation - * for current platform, including interpreting ".." and "." . - */ - public static final String toCanonicalForm(String path) - { - /*?? - if(path.indexOf('.') < 0 && path.indexOf("..") < 0) - return path; - */ - String tmppath = path.replace('/', separatorChar); - StringBuffer canonpath; - - int i; - - if ((i = beginWithRootPathPrefix(tmppath)) == 0 ) - return path; - - /* The original - "canonpath = new StringBuffer(tmppath.substring(0, i))" - isn't very efficient because StringBuffer's - ensureCapacity_unsynchronized will fail definitely each time - and will enlarge buffer and copy contents. . - */ - canonpath = new StringBuffer(INITIAL_MAX_PATH); - canonpath.append(tmppath.substring(0, i)); - tmppath = tmppath.substring(i); - // pathdepth==0 indicates there're only root path in the buffer - int pathdepth = 0; - - StringTokenizer st = new StringTokenizer(tmppath, separator); - - // Traverse each element of the path, handling "." and ".." - // Should handle "~" too? - if (st.hasMoreTokens()) - do - { - String s = st.nextToken(); - - // Handle "." or an empty element. - if (s.equals(".") || s.equals("")) - continue; - - // Handle ".." by deleting the last element from the path - if (s.equals("..")) - { - if (pathdepth == 0) - continue; - - // Strip of trailing separator - canonpath.setLength(canonpath.length() - 1/*separator.length()*/); - String tmpstr = canonpath.toString(); - int idx = tmpstr.lastIndexOf(separator); - - if ((idx == -1) || ((idx + 1/*separator.length()*/) > tmpstr.length())) - //throw new IOException("Can't happen error"); - return path; // Shouldn't happen - - canonpath.setLength(idx + 1/*separator.length()*/); - pathdepth--; - continue; - } - - canonpath.append(s); - pathdepth++; //now it's more than root path - - if (st.hasMoreTokens()) - canonpath.append(separator); - } - while (st.hasMoreTokens()); - - if (endWithSeparator(path)) - canonpath.append(separator); - - String tmpstr = canonpath.toString(); - //if (pathdepth > 0 && endWithSeparator(tmpstr) ) - // tmpstr = tmpstr.substring(0, tmpstr.length() - 1/*separator.length()*/); - - return tmpstr; - } - - /** - * This routine canonicalizes input param "path" to formal path representation - * for current platform, and normalize all separators to "sepchar". - */ - public static final String toCanonicalForm(String path, char sepchar) - { - String tmpstr = toCanonicalForm(path); - tmpstr = tmpstr.replace(separatorChar, sepchar); - return tmpstr; - } - - /** * This routine checks whether input param "path" ends with separator */ public static final boolean endWithSeparator(String path) diff --git a/gnu/java/nio/charset/Provider.java b/gnu/java/nio/charset/Provider.java index ad3b1da84..b56e5a90e 100644 --- a/gnu/java/nio/charset/Provider.java +++ b/gnu/java/nio/charset/Provider.java @@ -155,9 +155,9 @@ public final class Provider extends CharsetProvider /** * Load non-mandatory charsets. */ - private void loadExtended () + private synchronized void loadExtended () { - if(extendedLoaded) + if (extendedLoaded) return; addCharset (new ISO_8859_3 ()); // ISO-8859-3 aka ISO-LATIN-3 @@ -165,6 +165,12 @@ public final class Provider extends CharsetProvider addCharset (new ISO_8859_8 ()); // ISO-8859-8 (Hebrew) // Some more codepages + addCharset (new Cp424()); + addCharset (new Cp437()); + addCharset (new Cp737()); + addCharset (new Cp775()); + addCharset (new Cp850()); + addCharset (new Cp852()); addCharset (new Cp855()); // IBM Cyrillic addCharset (new Cp857()); // IBM Turkish addCharset (new Cp860()); // MSDOS Portugese @@ -176,6 +182,24 @@ public final class Provider extends CharsetProvider addCharset (new Cp866()); // MSDOS Russian addCharset (new Cp869()); // IBM modern Greek addCharset (new Cp874()); // IBM Thai + + addCharset (new MacCentralEurope()); + addCharset (new MacCroatian()); + addCharset (new MacCyrillic()); + addCharset (new MacDingbat()); + addCharset (new MacGreek()); + addCharset (new MacIceland()); + addCharset (new MacRoman()); + addCharset (new MacRomania()); + addCharset (new MacSymbol()); + addCharset (new MacThai()); + addCharset (new MacTurkish()); + addCharset (new MS874()); + + addCharset (new Windows1255()); + addCharset (new Windows1256()); + addCharset (new Windows1258()); + extendedLoaded = true; } @@ -199,7 +223,7 @@ public final class Provider extends CharsetProvider public Charset charsetForName (String charsetName) { Charset cs = (Charset) charsets.get(canonicalNames.get(charsetName.toLowerCase())); - if(cs == null && !extendedLoaded) + if (cs == null) { loadExtended(); cs = (Charset) charsets.get(canonicalNames.get(charsetName.toLowerCase())); diff --git a/gnu/java/security/PolicyFile.java b/gnu/java/security/PolicyFile.java index 3064f041b..8404eeb98 100644 --- a/gnu/java/security/PolicyFile.java +++ b/gnu/java/security/PolicyFile.java @@ -1,5 +1,5 @@ /* PolicyFile.java -- policy file reader - Copyright (C) 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -148,7 +148,7 @@ public final class PolicyFile extends Policy // Constants and fields. // ------------------------------------------------------------------------- - private static final Logger logger = SystemLogger.SYSTEM; + protected static final Logger logger = SystemLogger.SYSTEM; private static final String DEFAULT_POLICY = SystemProperties.getProperty("java.home") diff --git a/gnu/java/security/Properties.java b/gnu/java/security/Properties.java index 860b7d928..4e8bc01ea 100644 --- a/gnu/java/security/Properties.java +++ b/gnu/java/security/Properties.java @@ -38,13 +38,15 @@ exception statement from your version. */ package gnu.java.security; +import gnu.classpath.Configuration; + import java.io.FileInputStream; import java.io.IOException; -import java.io.PrintWriter; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.PropertyPermission; +import java.util.logging.Logger; /** * <p>A global object containing build-specific properties that affect the @@ -52,25 +54,7 @@ import java.util.PropertyPermission; */ public final class Properties { - - // Debugging methods and variables - // ------------------------------------------------------------------------- - - private static final String NAME = "Properties"; - - private static final boolean DEBUG = false; - - // private static final int debuglevel = 9; - private static final PrintWriter err = new PrintWriter(System.out, true); - - private static void debug(final String s) - { - err.println(">>> " + NAME + ": " + s); - } - - // Constants and variables - // ------------------------------------------------------------------------- - + private static final Logger log = Logger.getLogger(Properties.class.getName()); public static final String VERSION = "gnu.crypto.version"; public static final String PROPERTIES_FILE = "gnu.crypto.properties.file"; @@ -300,9 +284,8 @@ public final class Properties } catch (SecurityException se) { - if (DEBUG) - debug("Reading property " + PROPERTIES_FILE - + " not allowed. Ignored."); + if (Configuration.DEBUG) + log.fine("Reading property " + PROPERTIES_FILE + " not allowed. Ignored."); } if (propFile != null) { @@ -316,14 +299,14 @@ public final class Properties } catch (IOException ioe) { - if (DEBUG) - debug("IO error reading " + propFile + ": " + ioe.getMessage()); + if (Configuration.DEBUG) + log.fine("IO error reading " + propFile + ": " + ioe.getMessage()); } catch (SecurityException se) { - if (DEBUG) - debug("Security error reading " + propFile + ": " - + se.getMessage()); + if (Configuration.DEBUG) + log.fine("Security error reading " + propFile + ": " + + se.getMessage()); } } @@ -350,8 +333,8 @@ public final class Properties } catch (SecurityException x) { - if (DEBUG) - debug("SecurityManager forbids reading system properties. Ignored"); + if (Configuration.DEBUG) + log.fine("SecurityManager forbids reading system properties. Ignored"); } if (s != null) { @@ -360,14 +343,14 @@ public final class Properties // hide valid value set previously if (s.equals(TRUE) || s.equals(FALSE)) { - if (DEBUG) - debug("Setting " + name + " to '" + s + "'"); + if (Configuration.DEBUG) + log.fine("Setting " + name + " to '" + s + "'"); props.put(name, s); } else { - if (DEBUG) - debug("Invalid value for -D" + name + ": " + s + ". Ignored"); + if (Configuration.DEBUG) + log.fine("Invalid value for -D" + name + ": " + s + ". Ignored"); } } } diff --git a/gnu/java/security/Registry.java b/gnu/java/security/Registry.java index 0cb925135..9d8ceac8d 100644 --- a/gnu/java/security/Registry.java +++ b/gnu/java/security/Registry.java @@ -177,6 +177,9 @@ public interface Registry /** TLSv1 padding scheme. */ String TLS1_PAD = "tls1"; + /** ISO 10126-2 padding scheme. */ + String ISO10126_PAD = "iso10126"; + // Pseudo-random number generators.......................................... /** (Apparently) RC4 keystream PRNG. */ diff --git a/gnu/java/security/hash/Whirlpool.java b/gnu/java/security/hash/Whirlpool.java index b10fa53cd..ee40d92b3 100644 --- a/gnu/java/security/hash/Whirlpool.java +++ b/gnu/java/security/hash/Whirlpool.java @@ -38,9 +38,12 @@ exception statement from your version. */ package gnu.java.security.hash; +import gnu.classpath.Configuration; import gnu.java.security.Registry; import gnu.java.security.util.Util; +import java.util.logging.Logger; + /** * Whirlpool, a new 512-bit hashing function operating on messages less than * 2 ** 256 bits in length. The function structure is designed according to the @@ -61,16 +64,7 @@ import gnu.java.security.util.Util; */ public final class Whirlpool extends BaseHash { - // Debugging methods and variables - // ------------------------------------------------------------------------- - - private static final boolean DEBUG = false; - - private static final int debuglevel = 3; - - // Constants and variables - // ------------------------------------------------------------------------- - + private static final Logger log = Logger.getLogger(Whirlpool.class.getName()); private static final int BLOCK_SIZE = 64; // inner block size in bytes /** The digest of the 0-bit long message. */ @@ -183,95 +177,87 @@ public final class Whirlpool extends BaseHash ^ (T7[i++] & 0x00000000000000FFL); time = System.currentTimeMillis() - time; - if (DEBUG && debuglevel > 8) + if (Configuration.DEBUG) { - System.out.println("=========="); - System.out.println(); - System.out.println("Static data"); - System.out.println(); - - System.out.println(); - System.out.println("T0[]:"); + log.fine("Static data"); + log.fine("T0[]:"); + StringBuilder sb; for (i = 0; i < 64; i++) { + sb = new StringBuilder(); for (j = 0; j < 4; j++) - System.out.print("0x" + Util.toString(T0[i * 4 + j]) + ", "); + sb.append("0x").append(Util.toString(T0[i * 4 + j])).append(", "); - System.out.println(); + log.fine(sb.toString()); } - System.out.println(); - System.out.println("T1[]:"); + log.fine("T1[]:"); for (i = 0; i < 64; i++) { + sb = new StringBuilder(); for (j = 0; j < 4; j++) - System.out.print("0x" + Util.toString(T1[i * 4 + j]) + ", "); + sb.append("0x").append(Util.toString(T1[i * 4 + j])).append(", "); - System.out.println(); + log.fine(sb.toString()); } - System.out.println(); - System.out.println("T2[]:"); + log.fine("T2[]:"); for (i = 0; i < 64; i++) { + sb = new StringBuilder(); for (j = 0; j < 4; j++) - System.out.print("0x" + Util.toString(T2[i * 4 + j]) + ", "); + sb.append("0x").append(Util.toString(T2[i * 4 + j])).append(", "); - System.out.println(); + log.fine(sb.toString()); } - System.out.println(); - System.out.println("T3[]:"); + log.fine("T3[]:"); for (i = 0; i < 64; i++) { + sb = new StringBuilder(); for (j = 0; j < 4; j++) - System.out.print("0x" + Util.toString(T3[i * 4 + j]) + ", "); + sb.append("0x").append(Util.toString(T3[i * 4 + j])).append(", "); - System.out.println(); + log.fine(sb.toString()); } - System.out.println(); - System.out.println("T4[]:"); + log.fine("\nT4[]:"); for (i = 0; i < 64; i++) { + sb = new StringBuilder(); for (j = 0; j < 4; j++) - System.out.print("0x" + Util.toString(T4[i * 4 + j]) + ", "); + sb.append("0x").append(Util.toString(T4[i * 4 + j])).append(", "); - System.out.println(); + log.fine(sb.toString()); } - System.out.println(); - System.out.println("T5[]:"); + log.fine("T5[]:"); for (i = 0; i < 64; i++) { + sb = new StringBuilder(); for (j = 0; j < 4; j++) - System.out.print("0x" + Util.toString(T5[i * 4 + j]) + ", "); + sb.append("0x").append(Util.toString(T5[i * 4 + j])).append(", "); - System.out.println(); + log.fine(sb.toString()); } - System.out.println(); - System.out.println("T6[]:"); + log.fine("T6[]:"); for (i = 0; i < 64; i++) { + sb = new StringBuilder(); for (j = 0; j < 4; j++) - System.out.print("0x" + Util.toString(T5[i * 4 + j]) + ", "); + sb.append("0x").append(Util.toString(T5[i * 4 + j])).append(", "); - System.out.println(); + log.fine(sb.toString()); } - System.out.println(); - System.out.println("T7[]:"); + log.fine("T7[]:"); for (i = 0; i < 64; i++) { + sb = new StringBuilder(); for (j = 0; j < 4; j++) - System.out.print("0x" + Util.toString(T5[i * 4 + j]) + ", "); + sb.append("0x").append(Util.toString(T5[i * 4 + j])).append(", "); - System.out.println(); + log.fine(sb.toString()); } - System.out.println(); - System.out.println("rc[]:"); + log.fine("rc[]:"); for (i = 0; i < R; i++) - System.out.println("0x" + Util.toString(rc[i])); - - System.out.println(); + log.fine("0x" + Util.toString(rc[i])); - System.out.println(); - System.out.println("Total initialization time: " + time + " ms."); - System.out.println(); + log.fine("Total initialization time: " + time + " ms."); } } diff --git a/gnu/java/security/jce/sig/EncodedKeyFactory.java b/gnu/java/security/jce/sig/EncodedKeyFactory.java index 60152c279..bfee6cc76 100644 --- a/gnu/java/security/jce/sig/EncodedKeyFactory.java +++ b/gnu/java/security/jce/sig/EncodedKeyFactory.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.jce.sig; +import gnu.classpath.Configuration; import gnu.java.security.Registry; import gnu.java.security.key.dss.DSSPrivateKey; import gnu.java.security.key.dss.DSSPublicKey; @@ -196,7 +197,8 @@ public class EncodedKeyFactory protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { - log.entering(this.getClass().getName(), "engineGeneratePublic()", keySpec); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "engineGeneratePublic()", keySpec); PublicKey result = null; if (keySpec instanceof DSAPublicKeySpec) @@ -220,8 +222,9 @@ public class EncodedKeyFactory } catch (InvalidParameterException ignored) { - log.log(Level.FINE, "Exception in DSSPublicKey.valueOf(). Ignore", - ignored); + if (Configuration.DEBUG) + log.log(Level.FINE, "Exception in DSSPublicKey.valueOf(). Ignore", + ignored); } if (! ok) // try RSA @@ -232,23 +235,25 @@ public class EncodedKeyFactory } catch (InvalidParameterException ignored) { - log.log(Level.FINE, - "Exception in GnuRSAPublicKey.valueOf(). Ignore", - ignored); + if (Configuration.DEBUG) + log.log(Level.FINE, + "Exception in GnuRSAPublicKey.valueOf(). Ignore", + ignored); } if (! ok) // try DH result = decodeDHPublicKey(input); } - - log.exiting(this.getClass().getName(), "engineGeneratePublic()", result); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "engineGeneratePublic()", result); return result; } protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { - log.entering(this.getClass().getName(), "engineGeneratePrivate()", keySpec); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "engineGeneratePrivate()", keySpec); PrivateKey result = null; if (keySpec instanceof DSAPrivateKeySpec) @@ -272,8 +277,9 @@ public class EncodedKeyFactory } catch (InvalidParameterException ignored) { - log.log(Level.FINE, "Exception in DSSPrivateKey.valueOf(). Ignore", - ignored); + if (Configuration.DEBUG) + log.log(Level.FINE, "Exception in DSSPrivateKey.valueOf(). Ignore", + ignored); } if (! ok) // try RSA @@ -284,16 +290,17 @@ public class EncodedKeyFactory } catch (InvalidParameterException ignored) { - log.log(Level.FINE, - "Exception in GnuRSAPrivateKey.valueOf(). Ignore", - ignored); + if (Configuration.DEBUG) + log.log(Level.FINE, + "Exception in GnuRSAPrivateKey.valueOf(). Ignore", + ignored); } if (! ok) // try DH result = decodeDHPrivateKey(input); } - - log.exiting(this.getClass().getName(), "engineGeneratePrivate()", result); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "engineGeneratePrivate()", result); return result; } diff --git a/gnu/java/security/jce/sig/SignatureAdapter.java b/gnu/java/security/jce/sig/SignatureAdapter.java index 4dcbe78e5..724cc260e 100644 --- a/gnu/java/security/jce/sig/SignatureAdapter.java +++ b/gnu/java/security/jce/sig/SignatureAdapter.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.jce.sig; +import gnu.classpath.Configuration; import gnu.java.security.sig.BaseSignature; import gnu.java.security.sig.ISignature; import gnu.java.security.sig.ISignatureCodec; @@ -223,7 +224,8 @@ class SignatureAdapter extends SignatureSpi implements Cloneable public boolean engineVerify(byte[] sigBytes) throws SignatureException { - log.entering("SignatureAdapter", "engineVerify"); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "engineVerify"); Object signature = codec.decodeSignature(sigBytes); boolean result = false; @@ -235,8 +237,8 @@ class SignatureAdapter extends SignatureSpi implements Cloneable { throw new SignatureException(String.valueOf(x)); } - - log.exiting("SignatureAdapter", "engineVerify", new Boolean(result)); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "engineVerify", Boolean.valueOf(result)); return result; } diff --git a/gnu/java/security/key/dss/DSSKeyPairGenerator.java b/gnu/java/security/key/dss/DSSKeyPairGenerator.java index 5aa746147..ca24f36c5 100644 --- a/gnu/java/security/key/dss/DSSKeyPairGenerator.java +++ b/gnu/java/security/key/dss/DSSKeyPairGenerator.java @@ -38,12 +38,12 @@ exception statement from your version. */ package gnu.java.security.key.dss; +import gnu.classpath.Configuration; import gnu.java.security.Registry; import gnu.java.security.hash.Sha160; import gnu.java.security.key.IKeyPairGenerator; import gnu.java.security.util.PRNG; -import java.io.PrintWriter; import java.math.BigInteger; import java.security.KeyPair; import java.security.PrivateKey; @@ -51,6 +51,7 @@ import java.security.PublicKey; import java.security.SecureRandom; import java.security.spec.DSAParameterSpec; import java.util.Map; +import java.util.logging.Logger; /** * <p>A key-pair generator for asymetric keys to use in conjunction with the DSS @@ -63,26 +64,7 @@ import java.util.Map; */ public class DSSKeyPairGenerator implements IKeyPairGenerator { - - // Debugging methods and variables - // ------------------------------------------------------------------------- - - private static final String NAME = "dss"; - - private static final boolean DEBUG = false; - - private static final int debuglevel = 5; - - private static final PrintWriter err = new PrintWriter(System.out, true); - - private static void debug(String s) - { - err.println(">>> " + NAME + ": " + s); - } - - // Constants and variables - // ------------------------------------------------------------------------- - + private static final Logger log = Logger.getLogger(DSSKeyPairGenerator.class.getName()); /** The BigInteger constant 2. */ private static final BigInteger TWO = new BigInteger("2"); @@ -361,14 +343,14 @@ public class DSSKeyPairGenerator implements IKeyPairGenerator p = params[FIPS186.DSA_PARAMS_P]; e = params[FIPS186.DSA_PARAMS_E]; g = params[FIPS186.DSA_PARAMS_G]; - if (DEBUG && debuglevel > 0) + if (Configuration.DEBUG) { - debug("seed: " + seed.toString(16)); - debug("counter: " + counter.intValue()); - debug("q: " + q.toString(16)); - debug("p: " + p.toString(16)); - debug("e: " + e.toString(16)); - debug("g: " + g.toString(16)); + log.fine("seed: " + seed.toString(16)); + log.fine("counter: " + counter.intValue()); + log.fine("q: " + q.toString(16)); + log.fine("p: " + p.toString(16)); + log.fine("e: " + e.toString(16)); + log.fine("g: " + g.toString(16)); } } diff --git a/gnu/java/security/key/dss/DSSKeyPairPKCS8Codec.java b/gnu/java/security/key/dss/DSSKeyPairPKCS8Codec.java index 3a115b963..166178fe4 100644 --- a/gnu/java/security/key/dss/DSSKeyPairPKCS8Codec.java +++ b/gnu/java/security/key/dss/DSSKeyPairPKCS8Codec.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.key.dss; +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.Registry; import gnu.java.security.der.DER; @@ -184,7 +185,8 @@ public class DSSKeyPairPKCS8Codec */ public PrivateKey decodePrivateKey(byte[] input) { - log.entering("DSSKeyPairPKCS8Codec", "decodePrivateKey"); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "decodePrivateKey"); if (input == null) throw new InvalidParameterException("Input bytes MUST NOT be null"); @@ -226,9 +228,11 @@ public class DSSKeyPairPKCS8Codec g = (BigInteger) val.getValue(); val = der.read(); - log.finest("val = " + val); + if (Configuration.DEBUG) + log.fine("val = " + val); byte[] xBytes = (byte[]) val.getValue(); - log.finest(Util.dumpString(xBytes, "xBytes: ")); + if (Configuration.DEBUG) + log.fine(Util.dumpString(xBytes, "xBytes: ")); DERReader der2 = new DERReader(xBytes); val = der2.read(); DerUtil.checkIsBigInteger(val, "Wrong X field"); @@ -240,8 +244,8 @@ public class DSSKeyPairPKCS8Codec y.initCause(e); throw y; } - - log.exiting("DSSKeyPairPKCS8Codec", "decodePrivateKey"); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "decodePrivateKey"); return new DSSPrivateKey(Registry.PKCS8_ENCODING_ID, p, q, g, x); } } diff --git a/gnu/java/security/key/dss/DSSPrivateKey.java b/gnu/java/security/key/dss/DSSPrivateKey.java index fe59cb6d7..a9374557b 100644 --- a/gnu/java/security/key/dss/DSSPrivateKey.java +++ b/gnu/java/security/key/dss/DSSPrivateKey.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.key.dss; +import gnu.classpath.Configuration; import gnu.classpath.SystemProperties; import gnu.java.security.Registry; import gnu.java.security.key.IKeyPairCodec; @@ -53,11 +54,6 @@ import java.security.interfaces.DSAPrivateKey; */ public class DSSPrivateKey extends DSSKey implements PrivateKey, DSAPrivateKey { - // Constants and variables - // ------------------------------------------------------------------------- - - private static final boolean DEBUG = false; - /** * <p>A randomly or pseudorandomly generated integer with <code>0 < x < * q</code>.</p> @@ -210,7 +206,8 @@ public class DSSPrivateKey extends DSSKey implements PrivateKey, DSAPrivateKey String ls = SystemProperties.getProperty("line.separator"); str = new StringBuilder(this.getClass().getName()).append("(") .append(super.toString()).append(",").append(ls) - .append("x=0x").append(DEBUG ? x.toString(16) : "**...*").append(ls) + .append("x=0x").append(Configuration.DEBUG ? x.toString(16) + : "**...*").append(ls) .append(")").toString(); } diff --git a/gnu/java/security/key/rsa/GnuRSAPrivateKey.java b/gnu/java/security/key/rsa/GnuRSAPrivateKey.java index 920534487..bd39ee657 100644 --- a/gnu/java/security/key/rsa/GnuRSAPrivateKey.java +++ b/gnu/java/security/key/rsa/GnuRSAPrivateKey.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.key.rsa; +import gnu.classpath.Configuration; import gnu.classpath.SystemProperties; import gnu.java.security.Registry; import gnu.java.security.key.IKeyPairCodec; @@ -61,11 +62,6 @@ import java.security.interfaces.RSAPrivateKey; public class GnuRSAPrivateKey extends GnuRSAKey implements PrivateKey, RSAPrivateCrtKey { - // Constants and variables - // ------------------------------------------------------------------------- - - private static final boolean DEBUG = false; - /** The first prime divisor of the modulus. */ private final BigInteger p; @@ -307,12 +303,18 @@ public class GnuRSAPrivateKey extends GnuRSAKey implements PrivateKey, String ls = SystemProperties.getProperty("line.separator"); str = new StringBuilder(this.getClass().getName()).append("(") .append(super.toString()).append(",").append(ls) - .append("d=0x").append(DEBUG ? d.toString(16) : "**...*").append(ls) - .append("p=0x").append(DEBUG ? p.toString(16) : "**...*").append(ls) - .append("q=0x").append(DEBUG ? q.toString(16) : "**...*").append(ls) - .append("dP=0x").append(DEBUG ? dP.toString(16) : "**...*").append(ls) - .append("dQ=0x").append(DEBUG ? dQ.toString(16) : "**...*").append(ls) - .append("qInv=0x").append(DEBUG ? qInv.toString(16) : "**...*").append(ls) + .append("d=0x").append(Configuration.DEBUG ? d.toString(16) + : "**...*").append(ls) + .append("p=0x").append(Configuration.DEBUG ? p.toString(16) + : "**...*").append(ls) + .append("q=0x").append(Configuration.DEBUG ? q.toString(16) + : "**...*").append(ls) + .append("dP=0x").append(Configuration.DEBUG ? dP.toString(16) + : "**...*").append(ls) + .append("dQ=0x").append(Configuration.DEBUG ? dQ.toString(16) + : "**...*").append(ls) + .append("qInv=0x").append(Configuration.DEBUG ? qInv.toString(16) + : "**...*").append(ls) .append(")").toString(); } return str; diff --git a/gnu/java/security/key/rsa/RSAKeyPairGenerator.java b/gnu/java/security/key/rsa/RSAKeyPairGenerator.java index 39063381f..127b3eac4 100644 --- a/gnu/java/security/key/rsa/RSAKeyPairGenerator.java +++ b/gnu/java/security/key/rsa/RSAKeyPairGenerator.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.key.rsa; +import gnu.classpath.Configuration; import gnu.java.security.Registry; import gnu.java.security.key.IKeyPairGenerator; import gnu.java.security.util.PRNG; @@ -152,8 +153,8 @@ public class RSAKeyPairGenerator implements IKeyPairGenerator */ public void setup(Map attributes) { - log.entering(this.getClass().getName(), "setup", attributes); - + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "setup", attributes); // do we have a SecureRandom, or should we use our own? rnd = (SecureRandom) attributes.get(SOURCE_OF_RANDOMNESS); @@ -181,8 +182,8 @@ public class RSAKeyPairGenerator implements IKeyPairGenerator Integer formatID = (Integer) attributes.get(PREFERRED_ENCODING_FORMAT); preferredFormat = formatID == null ? DEFAULT_ENCODING_FORMAT : formatID.intValue(); - - log.exiting(this.getClass().getName(), "setup"); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "setup"); } /** @@ -193,7 +194,8 @@ public class RSAKeyPairGenerator implements IKeyPairGenerator */ public KeyPair generate() { - log.entering(this.getClass().getName(), "generate"); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "generate"); BigInteger p, q, n, d; @@ -243,7 +245,8 @@ public class RSAKeyPairGenerator implements IKeyPairGenerator PrivateKey secK = new GnuRSAPrivateKey(preferredFormat, p, q, e, d); KeyPair result = new KeyPair(pubK, secK); - log.exiting(this.getClass().getName(), "generate", result); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "generate", result); return result; } diff --git a/gnu/java/security/key/rsa/RSAKeyPairPKCS8Codec.java b/gnu/java/security/key/rsa/RSAKeyPairPKCS8Codec.java index 0b9809032..14a0a063c 100644 --- a/gnu/java/security/key/rsa/RSAKeyPairPKCS8Codec.java +++ b/gnu/java/security/key/rsa/RSAKeyPairPKCS8Codec.java @@ -38,15 +38,7 @@ exception statement from your version. */ package gnu.java.security.key.rsa; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.security.InvalidParameterException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.ArrayList; -import java.util.logging.Logger; - +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.Registry; import gnu.java.security.der.DER; @@ -56,6 +48,15 @@ import gnu.java.security.der.DERWriter; import gnu.java.security.key.IKeyPairCodec; import gnu.java.security.util.DerUtil; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.InvalidParameterException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.logging.Logger; + /** * An implementation of an {@link IKeyPairCodec} that knows how to encode / * decode PKCS#8 ASN.1 external representation of RSA private keys. @@ -122,7 +123,8 @@ public class RSAKeyPairPKCS8Codec */ public byte[] encodePrivateKey(PrivateKey key) { - log.entering(this.getClass().getName(), "encodePrivateKey()", key); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "encodePrivateKey()", key); if (! (key instanceof GnuRSAPrivateKey)) throw new InvalidParameterException("Wrong key type"); @@ -190,8 +192,8 @@ public class RSAKeyPairPKCS8Codec y.initCause(x); throw y; } - - log.exiting(this.getClass().getName(), "encodePrivateKey()", result); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "encodePrivateKey()", result); return result; } @@ -213,7 +215,8 @@ public class RSAKeyPairPKCS8Codec */ public PrivateKey decodePrivateKey(byte[] input) { - log.entering(this.getClass().getName(), "decodePrivateKey()", input); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "decodePrivateKey()", input); if (input == null) throw new InvalidParameterException("Input bytes MUST NOT be null"); @@ -287,7 +290,8 @@ public class RSAKeyPairPKCS8Codec PrivateKey result = new GnuRSAPrivateKey(Registry.PKCS8_ENCODING_ID, n, e, d, p, q, dP, dQ, qInv); - log.exiting(this.getClass().getName(), "decodePrivateKey()", result); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "decodePrivateKey()", result); return result; } } diff --git a/gnu/java/security/key/rsa/RSAKeyPairX509Codec.java b/gnu/java/security/key/rsa/RSAKeyPairX509Codec.java index 882d9c7b2..87c5c3a6d 100644 --- a/gnu/java/security/key/rsa/RSAKeyPairX509Codec.java +++ b/gnu/java/security/key/rsa/RSAKeyPairX509Codec.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.key.rsa; +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.Registry; import gnu.java.security.der.BitString; @@ -114,7 +115,8 @@ public class RSAKeyPairX509Codec */ public byte[] encodePublicKey(PublicKey key) { - log.entering(this.getClass().getName(), "encodePublicKey()", key); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "encodePublicKey()", key); if (! (key instanceof GnuRSAPublicKey)) throw new InvalidParameterException("key"); @@ -160,8 +162,8 @@ public class RSAKeyPairX509Codec y.initCause(x); throw y; } - - log.exiting(this.getClass().getName(), "encodePublicKey()", result); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "encodePublicKey()", result); return result; } @@ -183,7 +185,8 @@ public class RSAKeyPairX509Codec */ public PublicKey decodePublicKey(byte[] input) { - log.entering(this.getClass().getName(), "decodePublicKey()", input); + if (Configuration.DEBUG) + log.entering(this.getClass().getName(), "decodePublicKey()", input); if (input == null) throw new InvalidParameterException("Input bytes MUST NOT be null"); @@ -235,7 +238,8 @@ public class RSAKeyPairX509Codec } PublicKey result = new GnuRSAPublicKey(Registry.X509_ENCODING_ID, n, e); - log.exiting(this.getClass().getName(), "decodePublicKey()", result); + if (Configuration.DEBUG) + log.exiting(this.getClass().getName(), "decodePublicKey()", result); return result; } diff --git a/gnu/java/security/pkcs/PKCS7SignedData.java b/gnu/java/security/pkcs/PKCS7SignedData.java index 0781f4ba9..de2b2f679 100644 --- a/gnu/java/security/pkcs/PKCS7SignedData.java +++ b/gnu/java/security/pkcs/PKCS7SignedData.java @@ -37,6 +37,7 @@ exception statement from your version. */ package gnu.java.security.pkcs; +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.ber.BER; import gnu.java.security.ber.BEREncodingException; @@ -52,9 +53,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; - import java.math.BigInteger; - import java.security.cert.CRL; import java.security.cert.CRLException; import java.security.cert.Certificate; @@ -62,7 +61,6 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -174,21 +172,22 @@ public class PKCS7SignedData if (!val.isConstructed()) throw new BEREncodingException("malformed SignedData"); - log.finest("SignedData: " + val); + if (Configuration.DEBUG) + log.fine("SignedData: " + val); val = ber.read(); if (val.getTag() != BER.INTEGER) throw new BEREncodingException("expecting Version"); version = (BigInteger) val.getValue(); - - log.finest(" Version: " + version); + if (Configuration.DEBUG) + log.fine(" Version: " + version); digestAlgorithms = new HashSet(); val = ber.read(); if (!val.isConstructed()) throw new BEREncodingException("malformed DigestAlgorithmIdentifiers"); - - log.finest(" DigestAlgorithmIdentifiers: " + val); + if (Configuration.DEBUG) + log.fine(" DigestAlgorithmIdentifiers: " + val); int count = 0; DERValue val2 = ber.read(); while (val2 != BER.END_OF_SEQUENCE && @@ -196,14 +195,14 @@ public class PKCS7SignedData { if (!val2.isConstructed()) throw new BEREncodingException("malformed AlgorithmIdentifier"); - - log.finest(" AlgorithmIdentifier: " + val2); + if (Configuration.DEBUG) + log.fine(" AlgorithmIdentifier: " + val2); count += val2.getEncodedLength(); val2 = ber.read(); if (val2.getTag() != BER.OBJECT_IDENTIFIER) throw new BEREncodingException("malformed AlgorithmIdentifier"); - - log.finest(" digestAlgorithmIdentifiers OID: " + val2.getValue()); + if (Configuration.DEBUG) + log.fine(" digestAlgorithmIdentifiers OID: " + val2.getValue()); List algId = new ArrayList(2); algId.add(val2.getValue()); val2 = ber.read(); @@ -224,23 +223,27 @@ public class PKCS7SignedData else algId.add(null); - log.finest(" digestAlgorithmIdentifiers params: "); - log.finest(Util.dumpString((byte[]) algId.get(1), - " digestAlgorithmIdentifiers params: ")); + if (Configuration.DEBUG) + { + log.fine(" digestAlgorithmIdentifiers params: "); + log.fine(Util.dumpString((byte[]) algId.get(1), + " digestAlgorithmIdentifiers params: ")); + } digestAlgorithms.add(algId); } val = ber.read(); if (!val.isConstructed()) throw new BEREncodingException("malformed ContentInfo"); - - log.finest(" ContentInfo: " + val); + if (Configuration.DEBUG) + log.fine(" ContentInfo: " + val); val2 = ber.read(); if (val2.getTag() != BER.OBJECT_IDENTIFIER) throw new BEREncodingException("malformed ContentType"); contentType = (OID) val2.getValue(); - log.finest(" ContentType OID: " + contentType); + if (Configuration.DEBUG) + log.fine(" ContentType OID: " + contentType); if (BERValue.isIndefinite(val) || (val.getLength() > 0 && val.getLength() > val2.getEncodedLength())) { @@ -252,17 +255,18 @@ public class PKCS7SignedData val2 = ber.read(); } } - - log.finest(" Content: "); - log.finest(Util.dumpString(content, " Content: ")); - + if (Configuration.DEBUG) + { + log.fine(" Content: "); + log.fine(Util.dumpString(content, " Content: ")); + } val = ber.read(); if (val.getTag() == 0) { if (!val.isConstructed()) throw new BEREncodingException("malformed ExtendedCertificatesAndCertificates"); - - log.finest(" ExtendedCertificatesAndCertificates: " + val); + if (Configuration.DEBUG) + log.fine(" ExtendedCertificatesAndCertificates: " + val); count = 0; val2 = ber.read(); List certs = new LinkedList(); @@ -271,7 +275,8 @@ public class PKCS7SignedData { Certificate cert = x509.generateCertificate(new ByteArrayInputStream(val2.getEncoded())); - log.finest(" Certificate: " + cert); + if (Configuration.DEBUG) + log.fine(" Certificate: " + cert); certs.add(cert); count += val2.getEncodedLength(); ber.skip(val2.getLength()); @@ -286,8 +291,8 @@ public class PKCS7SignedData { if (!val.isConstructed()) throw new BEREncodingException("malformed CertificateRevocationLists"); - - log.finest(" CertificateRevocationLists: " + val); + if (Configuration.DEBUG) + log.fine(" CertificateRevocationLists: " + val); count = 0; val2 = ber.read(); List crls = new LinkedList(); @@ -295,7 +300,8 @@ public class PKCS7SignedData (val.getLength() > 0 && val.getLength() > count)) { CRL crl = x509.generateCRL(new ByteArrayInputStream(val2.getEncoded())); - log.finest(" CRL: " + crl); + if (Configuration.DEBUG) + log.fine(" CRL: " + crl); crls.add(crl); count += val2.getEncodedLength(); ber.skip(val2.getLength()); @@ -309,8 +315,8 @@ public class PKCS7SignedData signerInfos = new HashSet(); if (!val.isConstructed()) throw new BEREncodingException("malformed SignerInfos"); - - log.finest(" SignerInfos: " + val); + if (Configuration.DEBUG) + log.fine(" SignerInfos: " + val); // FIXME read this more carefully. // Since we are just reading a file (probably) we just read until we diff --git a/gnu/java/security/pkcs/SignerInfo.java b/gnu/java/security/pkcs/SignerInfo.java index 7b38bfefd..662bcc3b7 100644 --- a/gnu/java/security/pkcs/SignerInfo.java +++ b/gnu/java/security/pkcs/SignerInfo.java @@ -37,6 +37,7 @@ exception statement from your version. */ package gnu.java.security.pkcs; +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.ber.BER; import gnu.java.security.ber.BEREncodingException; @@ -50,7 +51,6 @@ import gnu.java.security.util.Util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; - import java.math.BigInteger; import java.util.ArrayList; import java.util.logging.Logger; @@ -101,7 +101,8 @@ public class SignerInfo public SignerInfo(BERReader ber) throws IOException { DERValue val = ber.read(); - log.finest("SignerInfo: " + val); + if (Configuration.DEBUG) + log.fine("SignerInfo: " + val); if (!val.isConstructed()) throw new BEREncodingException("malformed SignerInfo"); @@ -110,13 +111,13 @@ public class SignerInfo throw new BEREncodingException("malformed Version"); version = (BigInteger) val.getValue(); - log.finest(" Version: " + version); + log.fine(" Version: " + version); val = ber.read(); if (!val.isConstructed()) throw new BEREncodingException("malformed IssuerAndSerialNumber"); - - log.finest(" IssuerAndSerialNumber: " + val); + if (Configuration.DEBUG) + log.fine(" IssuerAndSerialNumber: " + val); val = ber.read(); if (!val.isConstructed()) @@ -124,20 +125,22 @@ public class SignerInfo issuer = new X500Principal(val.getEncoded()); ber.skip(val.getLength()); - log.finest(" Issuer: " + issuer); + if (Configuration.DEBUG) + log.fine(" Issuer: " + issuer); val = ber.read(); if (val.getTag() != BER.INTEGER) throw new BEREncodingException("malformed SerialNumber"); serialNumber = (BigInteger) val.getValue(); - log.finest(" SerialNumber: " + serialNumber); + if (Configuration.DEBUG) + log.fine(" SerialNumber: " + serialNumber); val = ber.read(); if (!val.isConstructed()) throw new BEREncodingException("malformed DigestAlgorithmIdentifier"); - - log.finest(" DigestAlgorithmIdentifier: " + val); + if (Configuration.DEBUG) + log.fine(" DigestAlgorithmIdentifier: " + val); int count = 0; DERValue val2 = ber.read(); @@ -145,7 +148,8 @@ public class SignerInfo throw new BEREncodingException("malformed AlgorithmIdentifier"); digestAlgorithmId = (OID) val2.getValue(); - log.finest(" digestAlgorithm OID: " + digestAlgorithmId); + if (Configuration.DEBUG) + log.fine(" digestAlgorithm OID: " + digestAlgorithmId); if (BERValue.isIndefinite(val)) { @@ -170,10 +174,12 @@ public class SignerInfo else digestAlgorithmParams = null; - log.finest(" digestAlgorithm params: "); - log.finest(Util.dumpString(digestAlgorithmParams, - " digestAlgorithm params: ")); - + if (Configuration.DEBUG) + { + log.fine(" digestAlgorithm params: "); + log.fine(Util.dumpString(digestAlgorithmParams, + " digestAlgorithm params: ")); + } val = ber.read(); if (val.getTag() == 0) { @@ -187,21 +193,24 @@ public class SignerInfo else authenticatedAttributes = null; - log.finest(" AuthenticatedAttributes: "); - log.finest(Util.dumpString(authenticatedAttributes, - " AuthenticatedAttributes: ")); - + if (Configuration.DEBUG) + { + log.fine(" AuthenticatedAttributes: "); + log.fine(Util.dumpString(authenticatedAttributes, + " AuthenticatedAttributes: ")); + } if (!val.isConstructed()) throw new BEREncodingException("malformed DigestEncryptionAlgorithmIdentifier"); - - log.finest(" DigestEncryptionAlgorithmIdentifier: " + val); + if (Configuration.DEBUG) + log.fine(" DigestEncryptionAlgorithmIdentifier: " + val); count = 0; val2 = ber.read(); if (val2.getTag() != BER.OBJECT_IDENTIFIER) throw new BEREncodingException("malformed AlgorithmIdentifier"); digestEncryptionAlgorithmId = (OID) val2.getValue(); - log.finest(" digestEncryptionAlgorithm OID: " + digestEncryptionAlgorithmId); + if (Configuration.DEBUG) + log.fine(" digestEncryptionAlgorithm OID: " + digestEncryptionAlgorithmId); if (BERValue.isIndefinite(val)) { @@ -226,27 +235,33 @@ public class SignerInfo else digestEncryptionAlgorithmParams = null; - log.finest(" digestEncryptionAlgorithm params: "); - log.finest(Util.dumpString(digestEncryptionAlgorithmParams, - " digestEncryptionAlgorithm params: ")); - + if (Configuration.DEBUG) + { + log.fine(" digestEncryptionAlgorithm params: "); + log.fine(Util.dumpString(digestEncryptionAlgorithmParams, + " digestEncryptionAlgorithm params: ")); + } val = ber.read(); if (val.getTag() != BER.OCTET_STRING) throw new BEREncodingException("malformed EncryptedDigest"); encryptedDigest = (byte[]) val.getValue(); - log.finest(" EncryptedDigest: "); - log.finest(Util.dumpString(encryptedDigest, " EncryptedDigest: ")); - + if (Configuration.DEBUG) + { + log.fine(" EncryptedDigest: "); + log.fine(Util.dumpString(encryptedDigest, " EncryptedDigest: ")); + } if (ber.peek() == 1) unauthenticatedAttributes = ber.read().getEncoded(); else unauthenticatedAttributes = null; - log.finest(" UnauthenticatedAttributes: "); - log.finest(Util.dumpString(unauthenticatedAttributes, - " UnauthenticatedAttributes: ")); - + if (Configuration.DEBUG) + { + log.fine(" UnauthenticatedAttributes: "); + log.fine(Util.dumpString(unauthenticatedAttributes, + " UnauthenticatedAttributes: ")); + } if (ber.peek() == 0) ber.read(); } diff --git a/gnu/java/security/provider/PKIXCertPathValidatorImpl.java b/gnu/java/security/provider/PKIXCertPathValidatorImpl.java index 880163731..3680f2fae 100644 --- a/gnu/java/security/provider/PKIXCertPathValidatorImpl.java +++ b/gnu/java/security/provider/PKIXCertPathValidatorImpl.java @@ -1,5 +1,5 @@ /* PKIXCertPathValidatorImpl.java -- PKIX certificate path validator. - Copyright (C) 2004, 2005 Free Software Foundation, Inc. + Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.provider; +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.Registry; import gnu.java.security.key.dss.DSSPublicKey; @@ -81,6 +82,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; +import java.util.logging.Logger; /** * An implementation of the Public Key Infrastructure's X.509 @@ -94,17 +96,7 @@ import java.util.Set; */ public class PKIXCertPathValidatorImpl extends CertPathValidatorSpi { - - // Constants. - // ------------------------------------------------------------------------- - - private static final boolean DEBUG = false; - private static void debug (String msg) - { - System.err.print (">> PKIXCertPathValidatorImpl: "); - System.err.println (msg); - } - + private static final Logger log = Logger.getLogger(PKIXCertPathValidatorImpl.class.getName()); public static final String ANY_POLICY = "2.5.29.32.0"; // Constructor. @@ -435,7 +427,7 @@ public class PKIXCertPathValidatorImpl extends CertPathValidatorSpi * @param crl The CRL being checked. * @param path The path this CRL is being checked against. * @param now The value to use as 'now'. - * @param pubKeySubject The subject of the public key. + * @param pubKeyCert The certificate authenticating the public key. * @param pubKey The public key to check. * @return True if the CRL is acceptable. */ @@ -603,7 +595,8 @@ public class PKIXCertPathValidatorImpl extends CertPathValidatorSpi boolean explicitPolicy) throws CertPathValidatorException { - if (DEBUG) debug("updatePolicyTree depth == " + depth); + if (Configuration.DEBUG) + log.fine("updatePolicyTree depth == " + depth); Set nodes = new HashSet(); LinkedList stack = new LinkedList(); Iterator current = null; @@ -614,15 +607,18 @@ public class PKIXCertPathValidatorImpl extends CertPathValidatorSpi while (current.hasNext()) { PolicyNodeImpl p = (PolicyNodeImpl) current.next(); - if (DEBUG) debug("visiting node == " + p); + if (Configuration.DEBUG) + log.fine("visiting node == " + p); if (p.getDepth() == depth - 1) { - if (DEBUG) debug("added node"); + if (Configuration.DEBUG) + log.fine("added node"); nodes.add(p); } else { - if (DEBUG) debug("skipped node"); + if (Configuration.DEBUG) + log.fine("skipped node"); stack.addLast(current); current = p.getChildren(); } @@ -646,16 +642,21 @@ public class PKIXCertPathValidatorImpl extends CertPathValidatorSpi else cp = Collections.EMPTY_LIST; boolean match = false; - if (DEBUG) debug("nodes are == " + nodes); - if (DEBUG) debug("cert policies are == " + cp); + if (Configuration.DEBUG) + { + log.fine("nodes are == " + nodes); + log.fine("cert policies are == " + cp); + } for (Iterator it = nodes.iterator(); it.hasNext(); ) { PolicyNodeImpl parent = (PolicyNodeImpl) it.next(); - if (DEBUG) debug("adding policies to " + parent); + if (Configuration.DEBUG) + log.fine("adding policies to " + parent); for (Iterator it2 = cp.iterator(); it2.hasNext(); ) { OID policy = (OID) it2.next(); - if (DEBUG) debug("trying to add policy == " + policy); + if (Configuration.DEBUG) + log.fine("trying to add policy == " + policy); if (policy.toString().equals(ANY_POLICY) && params.isAnyPolicyInhibited()) continue; @@ -691,13 +692,15 @@ public class PKIXCertPathValidatorImpl extends CertPathValidatorSpi private boolean checkExplicitPolicy (int depth, List explicitPolicies) { - if (DEBUG) debug ("checkExplicitPolicy depth=" + depth); + if (Configuration.DEBUG) + log.fine("checkExplicitPolicy depth=" + depth); for (Iterator it = explicitPolicies.iterator(); it.hasNext(); ) { int[] i = (int[]) it.next(); int caDepth = i[0]; int limit = i[1]; - if (DEBUG) debug (" caDepth=" + caDepth + " limit=" + limit); + if (Configuration.DEBUG) + log.fine(" caDepth=" + caDepth + " limit=" + limit); if (depth - caDepth >= limit) return true; } diff --git a/gnu/java/security/sig/ISignature.java b/gnu/java/security/sig/ISignature.java index e77f39d2c..9ad853a29 100644 --- a/gnu/java/security/sig/ISignature.java +++ b/gnu/java/security/sig/ISignature.java @@ -71,9 +71,9 @@ public interface ISignature extends Cloneable /** * Property name of an optional {@link java.security.SecureRandom}, - * {@link java.util.Random}, or {@link gnu.crypto.prng.IRandom} instance to + * {@link java.util.Random}, or {@link gnu.java.security.prng.IRandom} instance to * use. The default is to use a classloader singleton from - * {@link gnu.crypto.util.PRNG}. + * {@link gnu.java.security.util.PRNG}. */ public static final String SOURCE_OF_RANDOMNESS = "gnu.crypto.sig.prng"; diff --git a/gnu/java/security/sig/rsa/EMSA_PSS.java b/gnu/java/security/sig/rsa/EMSA_PSS.java index c1c9760ed..0b93abab7 100644 --- a/gnu/java/security/sig/rsa/EMSA_PSS.java +++ b/gnu/java/security/sig/rsa/EMSA_PSS.java @@ -38,12 +38,13 @@ exception statement from your version. */ package gnu.java.security.sig.rsa; +import gnu.classpath.Configuration; import gnu.java.security.hash.HashFactory; import gnu.java.security.hash.IMessageDigest; import gnu.java.security.util.Util; -import java.io.PrintWriter; import java.util.Arrays; +import java.util.logging.Logger; /** * <p>An implementation of the EMSA-PSS encoding/decoding scheme.</p> @@ -70,26 +71,7 @@ import java.util.Arrays; */ public class EMSA_PSS implements Cloneable { - - // Debugging methods and variables - // ------------------------------------------------------------------------- - - private static final String NAME = "emsa-pss"; - - private static final boolean DEBUG = false; - - private static final int debuglevel = 5; - - private static final PrintWriter err = new PrintWriter(System.out, true); - - private static void debug(String s) - { - err.println(">>> " + NAME + ": " + s); - } - - // Constants and variables - // ------------------------------------------------------------------------- - + private static final Logger log = Logger.getLogger(EMSA_PSS.class.getName()); /** The underlying hash function to use with this instance. */ private IMessageDigest hash; @@ -202,10 +184,10 @@ public class EMSA_PSS implements Cloneable System.arraycopy(salt, 0, DB, emLen - sLen - hLen - 1, sLen); // 9. Let dbMask = MGF(H, emLen - hLen - 1). byte[] dbMask = MGF(H, emLen - hLen - 1); - if (DEBUG && debuglevel > 8) + if (Configuration.DEBUG) { - debug("dbMask (encode): " + Util.toString(dbMask)); - debug("DB (encode): " + Util.toString(DB)); + log.fine("dbMask (encode): " + Util.toString(dbMask)); + log.fine("DB (encode): " + Util.toString(DB)); } // 10. Let maskedDB = DB XOR dbMask. for (i = 0; i < DB.length; i++) @@ -244,12 +226,12 @@ public class EMSA_PSS implements Cloneable */ public boolean decode(byte[] mHash, byte[] EM, int emBits, int sLen) { - if (DEBUG && debuglevel > 8) + if (Configuration.DEBUG) { - debug("mHash: " + Util.toString(mHash)); - debug("EM: " + Util.toString(EM)); - debug("emBits: " + String.valueOf(emBits)); - debug("sLen: " + String.valueOf(sLen)); + log.fine("mHash: " + Util.toString(mHash)); + log.fine("EM: " + Util.toString(EM)); + log.fine("emBits: " + String.valueOf(emBits)); + log.fine("sLen: " + String.valueOf(sLen)); } if (sLen < 0) { @@ -262,19 +244,15 @@ public class EMSA_PSS implements Cloneable // 2. Let mHash = Hash(M), an octet string of length hLen. if (hLen != mHash.length) { - if (DEBUG && debuglevel > 8) - { - debug("hLen != mHash.length; hLen: " + String.valueOf(hLen)); - } + if (Configuration.DEBUG) + log.fine("hLen != mHash.length; hLen: " + String.valueOf(hLen)); throw new IllegalArgumentException("wrong hash"); } // 3. If emBits < 8.hLen + 8.sLen + 9, output 'decoding error' and stop. if (emBits < (8 * hLen + 8 * sLen + 9)) { - if (DEBUG && debuglevel > 8) - { - debug("emBits < (8hLen + 8sLen + 9); sLen: " + String.valueOf(sLen)); - } + if (Configuration.DEBUG) + log.fine("emBits < (8hLen + 8sLen + 9); sLen: " + String.valueOf(sLen)); throw new IllegalArgumentException("decoding error"); } int emLen = (emBits + 7) / 8; @@ -282,10 +260,8 @@ public class EMSA_PSS implements Cloneable // output 'inconsistent' and stop. if ((EM[EM.length - 1] & 0xFF) != 0xBC) { - if (DEBUG && debuglevel > 8) - { - debug("EM does not end with 0xBC"); - } + if (Configuration.DEBUG) + log.fine("EM does not end with 0xBC"); return false; } // 5. Let maskedDB be the leftmost emLen ? hLen ? 1 octets of EM, and let @@ -294,10 +270,8 @@ public class EMSA_PSS implements Cloneable // maskedDB are not all equal to zero, output 'inconsistent' and stop. if ((EM[0] & (0xFF << (8 - (8 * emLen - emBits)))) != 0) { - if (DEBUG && debuglevel > 8) - { - debug("Leftmost 8emLen - emBits bits of EM are not 0s"); - } + if (Configuration.DEBUG) + log.fine("Leftmost 8emLen - emBits bits of EM are not 0s"); return false; } byte[] DB = new byte[emLen - hLen - 1]; @@ -314,10 +288,10 @@ public class EMSA_PSS implements Cloneable } // 9. Set the leftmost 8.emLen ? emBits bits of DB to zero. DB[0] &= (0xFF >>> (8 * emLen - emBits)); - if (DEBUG && debuglevel > 8) + if (Configuration.DEBUG) { - debug("dbMask (decode): " + Util.toString(dbMask)); - debug("DB (decode): " + Util.toString(DB)); + log.fine("dbMask (decode): " + Util.toString(dbMask)); + log.fine("DB (decode): " + Util.toString(DB)); } // 10. If the emLen -hLen -sLen -2 leftmost octets of DB are not zero or // if the octet at position emLen -hLen -sLen -1 is not equal to 0x01, @@ -329,20 +303,16 @@ public class EMSA_PSS implements Cloneable { if (DB[i] != 0) { - if (DEBUG && debuglevel > 8) - { - debug("DB[" + String.valueOf(i) + "] != 0x00"); - } + if (Configuration.DEBUG) + log.fine("DB[" + String.valueOf(i) + "] != 0x00"); return false; } } if (DB[i] != 0x01) { // i == emLen -hLen -sLen -2 - if (DEBUG && debuglevel > 8) - { - debug("DB's byte at position (emLen -hLen -sLen -2); i.e. " - + String.valueOf(i) + " is not 0x01"); - } + if (Configuration.DEBUG) + log.fine("DB's byte at position (emLen -hLen -sLen -2); i.e. " + + String.valueOf(i) + " is not 0x01"); return false; } // 11. Let salt be the last sLen octets of DB. diff --git a/gnu/java/security/sig/rsa/RSAPSSSignature.java b/gnu/java/security/sig/rsa/RSAPSSSignature.java index 7ec62568a..3e9cad452 100644 --- a/gnu/java/security/sig/rsa/RSAPSSSignature.java +++ b/gnu/java/security/sig/rsa/RSAPSSSignature.java @@ -38,18 +38,19 @@ exception statement from your version. */ package gnu.java.security.sig.rsa; +import gnu.classpath.Configuration; import gnu.java.security.Registry; import gnu.java.security.hash.HashFactory; import gnu.java.security.hash.IMessageDigest; import gnu.java.security.sig.BaseSignature; import gnu.java.security.util.Util; -import java.io.PrintWriter; import java.math.BigInteger; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.util.logging.Logger; /** * <p>The RSA-PSS signature scheme is a public-key encryption scheme combining @@ -73,26 +74,7 @@ import java.security.interfaces.RSAPublicKey; */ public class RSAPSSSignature extends BaseSignature { - - // Debugging methods and variables - // ------------------------------------------------------------------------- - - private static final String NAME = "rsa-pss"; - - private static final boolean DEBUG = false; - - private static final int debuglevel = 1; - - private static final PrintWriter err = new PrintWriter(System.out, true); - - private static void debug(String s) - { - err.println(">>> " + NAME + ": " + s); - } - - // Constants and variables - // ------------------------------------------------------------------------- - + private static final Logger log = Logger.getLogger(RSAPSSSignature.class.getName()); /** The underlying EMSA-PSS instance for this object. */ private EMSA_PSS pss; @@ -201,10 +183,8 @@ public class RSAPSSSignature extends BaseSignature byte[] salt = new byte[sLen]; this.nextRandomBytes(salt); byte[] EM = pss.encode(md.digest(), modBits - 1, salt); - if (DEBUG && debuglevel > 8) - { - debug("EM (sign): " + Util.toString(EM)); - } + if (Configuration.DEBUG) + log.fine("EM (sign): " + Util.toString(EM)); // 2. Convert the encoded message EM to an integer message representative // m (see Section 1.2.2): m = OS2IP(EM). BigInteger m = new BigInteger(1, EM); @@ -262,10 +242,8 @@ public class RSAPSSSignature extends BaseSignature int emBits = modBits - 1; int emLen = (emBits + 7) / 8; byte[] EM = m.toByteArray(); - if (DEBUG && debuglevel > 8) - { - debug("EM (verify): " + Util.toString(EM)); - } + if (Configuration.DEBUG) + log.fine("EM (verify): " + Util.toString(EM)); if (EM.length > emLen) { return false; diff --git a/gnu/java/security/sig/rsa/RSAPSSSignatureRawCodec.java b/gnu/java/security/sig/rsa/RSAPSSSignatureRawCodec.java index 2be79165f..1ae295e36 100644 --- a/gnu/java/security/sig/rsa/RSAPSSSignatureRawCodec.java +++ b/gnu/java/security/sig/rsa/RSAPSSSignatureRawCodec.java @@ -44,7 +44,7 @@ import gnu.java.security.sig.ISignatureCodec; import java.io.ByteArrayOutputStream; /** - * <p>An object that implements the {@link gnu.crypto.sig.ISignatureCodec} + * <p>An object that implements the {@link ISignatureCodec} * operations for the <i>Raw</i> format to use with RSA-PSS signatures.</p> */ public class RSAPSSSignatureRawCodec implements ISignatureCodec diff --git a/gnu/java/security/util/Base64.java b/gnu/java/security/util/Base64.java index f9998c38f..6c4657b7c 100644 --- a/gnu/java/security/util/Base64.java +++ b/gnu/java/security/util/Base64.java @@ -38,8 +38,10 @@ exception statement from your version. */ package gnu.java.security.util; -import java.io.PrintWriter; +import gnu.classpath.Configuration; + import java.io.UnsupportedEncodingException; +import java.util.logging.Logger; /** * Most of this implementation is from Robert Harder's public domain Base64 @@ -47,26 +49,7 @@ import java.io.UnsupportedEncodingException; */ public class Base64 { - - // Debugging methods and variables - // ------------------------------------------------------------------------- - - private static final String NAME = "Base64"; - - private static final boolean DEBUG = true; - - private static final int debuglevel = 9; - - private static final PrintWriter err = new PrintWriter(System.out, true); - - private static void debug(String s) - { - err.println(">>> " + NAME + ": " + s); - } - - // Constants and variables - // ------------------------------------------------------------------------- - + private static final Logger log = Logger.getLogger(Base64.class.getName()); /** Maximum line length (76) of Base64 output. */ private static final int MAX_LINE_LENGTH = 76; @@ -383,12 +366,12 @@ public class Base64 } catch (Exception x) { - if (DEBUG && debuglevel > 8) + if (Configuration.DEBUG) { - debug("" + src[sOffset] + ": " + (DECODABET[src[sOffset]])); - debug("" + src[sOffset + 1] + ": " + (DECODABET[src[sOffset + 1]])); - debug("" + src[sOffset + 2] + ": " + (DECODABET[src[sOffset + 2]])); - debug("" + src[sOffset + 3] + ": " + (DECODABET[src[sOffset + 3]])); + log.fine("" + src[sOffset ] + ": " + (DECODABET[src[sOffset ]])); + log.fine("" + src[sOffset + 1] + ": " + (DECODABET[src[sOffset + 1]])); + log.fine("" + src[sOffset + 2] + ": " + (DECODABET[src[sOffset + 2]])); + log.fine("" + src[sOffset + 3] + ": " + (DECODABET[src[sOffset + 3]])); } return -1; } diff --git a/gnu/java/security/util/ExpirableObject.java b/gnu/java/security/util/ExpirableObject.java index 2d4452015..c14b75957 100644 --- a/gnu/java/security/util/ExpirableObject.java +++ b/gnu/java/security/util/ExpirableObject.java @@ -51,7 +51,7 @@ import javax.security.auth.Destroyable; * once a timeout elapses, will automatically call the {@link * Destroyable#destroy()} method. * - * <p>Concrete subclasses must implement the {@link doDestroy()} method + * <p>Concrete subclasses must implement the {@link #doDestroy()} method * instead of {@link Destroyable#destroy()}; the behavior of that method * should match exactly the behavior desired of <code>destroy()</code>. * @@ -111,7 +111,7 @@ public abstract class ExpirableObject implements Destroyable // ------------------------------------------------------------------------- /** - * Destroys this object. This method calls {@link doDestroy}, then, if + * Destroys this object. This method calls {@link #doDestroy}, then, if * no exception is thrown, cancels the task that would destroy this object * when the timeout is reached. * diff --git a/gnu/java/security/util/Prime2.java b/gnu/java/security/util/Prime2.java index 6e46f5fca..49e4072f6 100644 --- a/gnu/java/security/util/Prime2.java +++ b/gnu/java/security/util/Prime2.java @@ -38,37 +38,20 @@ exception statement from your version. */ package gnu.java.security.util; -import java.io.PrintWriter; +import gnu.classpath.Configuration; + import java.lang.ref.WeakReference; import java.math.BigInteger; import java.util.Map; import java.util.WeakHashMap; +import java.util.logging.Logger; /** * <p>A collection of prime number related utilities used in this library.</p> */ public class Prime2 { - - // Debugging methods and variables - // ------------------------------------------------------------------------- - - private static final String NAME = "prime"; - - private static final boolean DEBUG = false; - - private static final int debuglevel = 5; - - private static final PrintWriter err = new PrintWriter(System.out, true); - - private static void debug(String s) - { - err.println(">>> " + NAME + ": " + s); - } - - // Constants and variables - // ------------------------------------------------------------------------- - + private static final Logger log = Logger.getLogger(Prime2.class.getName()); private static final int DEFAULT_CERTAINTY = 20; // XXX is this a good value? private static final BigInteger ZERO = BigInteger.ZERO; @@ -116,7 +99,7 @@ public class Prime2 } } time += System.currentTimeMillis(); - if (DEBUG && debuglevel > 8) + if (Configuration.DEBUG) { StringBuffer sb; for (int i = 0; i < (SMALL_PRIME_COUNT / 10); i++) @@ -126,13 +109,13 @@ public class Prime2 { sb.append(String.valueOf(SMALL_PRIME[i * 10 + j])).append(" "); } - debug(sb.toString()); + log.fine(sb.toString()); } } - if (DEBUG && debuglevel > 4) + if (Configuration.DEBUG) { - debug("Generating first " + String.valueOf(SMALL_PRIME_COUNT) - + " primes took: " + String.valueOf(time) + " ms."); + log.fine("Generating first " + String.valueOf(SMALL_PRIME_COUNT) + + " primes took: " + String.valueOf(time) + " ms."); } } @@ -169,17 +152,13 @@ public class Prime2 prime = SMALL_PRIME[i]; if (w.mod(prime).equals(ZERO)) { - if (DEBUG && debuglevel > 4) - { - debug(prime.toString(16) + " | " + w.toString(16) + "..."); - } + if (Configuration.DEBUG) + log.fine(prime.toString(16) + " | " + w.toString(16) + "..."); return true; } } - if (DEBUG && debuglevel > 4) - { - debug(w.toString(16) + " has no small prime divisors..."); - } + if (Configuration.DEBUG) + log.fine(w.toString(16) + " has no small prime divisors..."); return false; } @@ -361,8 +340,8 @@ public class Prime2 for (int i = 0; i < SMALL_PRIME_COUNT; i++) if (w.equals(SMALL_PRIME[i])) { - if (DEBUG && debuglevel > 4) - debug(w.toString(16) + " is a small prime"); + if (Configuration.DEBUG) + log.fine(w.toString(16) + " is a small prime"); return true; } @@ -370,16 +349,16 @@ public class Prime2 WeakReference obj = (WeakReference) knownPrimes.get(w); if (obj != null && w.equals(obj.get())) { - if (DEBUG && debuglevel > 4) - debug("found in known primes"); + if (Configuration.DEBUG) + log.fine("found in known primes"); return true; } // trial division with first 1000 primes if (hasSmallPrimeDivisor(w)) { - if (DEBUG && debuglevel > 4) - debug(w.toString(16) + " has a small prime divisor. Rejected..."); + if (Configuration.DEBUG) + log.fine(w.toString(16) + " has a small prime divisor. Rejected..."); return false; } @@ -411,7 +390,7 @@ public class Prime2 private static final void debugBI(String msg, BigInteger bn) { - if (DEBUG && debuglevel > 4) - debug("*** " + msg + ": 0x" + bn.toString(16)); + if (Configuration.DEBUG) + log.fine("*** " + msg + ": 0x" + bn.toString(16)); } } diff --git a/gnu/java/security/util/SimpleList.java b/gnu/java/security/util/SimpleList.java index b2525c4b8..8636b4e14 100644 --- a/gnu/java/security/util/SimpleList.java +++ b/gnu/java/security/util/SimpleList.java @@ -61,7 +61,7 @@ public final class SimpleList extends AbstractList /** * Create a singleton list. * - * @param e1 The first element. + * @param element The first element. */ public SimpleList(final Object element) { diff --git a/gnu/java/security/x509/Util.java b/gnu/java/security/x509/Util.java index d27392026..1bd268a51 100644 --- a/gnu/java/security/x509/Util.java +++ b/gnu/java/security/x509/Util.java @@ -1,5 +1,5 @@ /* Util.java -- Miscellaneous utility methods. - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -155,7 +155,7 @@ public final class Util } /** - * See {@link #hexDump(byte[],int,int)}. + * See {@link #hexDump(byte[],int,int,String)}. */ public static String hexDump(byte[] buf, String prefix) { diff --git a/gnu/java/security/x509/X509CRL.java b/gnu/java/security/x509/X509CRL.java index 5b2d3b141..d8cbe988b 100644 --- a/gnu/java/security/x509/X509CRL.java +++ b/gnu/java/security/x509/X509CRL.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.x509; +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.der.BitString; import gnu.java.security.der.DER; @@ -64,6 +65,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.logging.Logger; import javax.security.auth.x500.X500Principal; @@ -75,20 +77,7 @@ import javax.security.auth.x500.X500Principal; public class X509CRL extends java.security.cert.X509CRL implements GnuPKIExtension { - - // Constants and fields. - // ------------------------------------------------------------------------ - - private static final boolean DEBUG = false; - private static void debug(String msg) - { - if (DEBUG) - { - System.err.print(">> X509CRL: "); - System.err.println(msg); - } - } - + private static final Logger log = Logger.getLogger(X509CRL.class.getName()); private static final OID ID_DSA = new OID("1.2.840.10040.4.1"); private static final OID ID_DSA_WITH_SHA1 = new OID("1.2.840.10040.4.3"); private static final OID ID_RSA = new OID("1.2.840.113549.1.1.1"); @@ -350,7 +339,8 @@ public class X509CRL extends java.security.cert.X509CRL // CertificateList ::= SEQUENCE { DERReader der = new DERReader(in); DERValue val = der.read(); - debug("start CertificateList len == " + val.getLength()); + if (Configuration.DEBUG) + log.fine("start CertificateList len == " + val.getLength()); if (!val.isConstructed()) throw new IOException("malformed CertificateList"); encoded = val.getEncoded(); @@ -359,7 +349,8 @@ public class X509CRL extends java.security.cert.X509CRL val = der.read(); if (!val.isConstructed()) throw new IOException("malformed TBSCertList"); - debug("start tbsCertList len == " + val.getLength()); + if (Configuration.DEBUG) + log.fine("start tbsCertList len == " + val.getLength()); tbsCRLBytes = val.getEncoded(); // version Version OPTIONAL, @@ -372,19 +363,23 @@ public class X509CRL extends java.security.cert.X509CRL } else version = 1; - debug("read version == " + version); + if (Configuration.DEBUG) + log.fine("read version == " + version); // signature AlgorithmIdentifier, - debug("start AlgorithmIdentifier len == " + val.getLength()); + if (Configuration.DEBUG) + log.fine("start AlgorithmIdentifier len == " + val.getLength()); if (!val.isConstructed()) throw new IOException("malformed AlgorithmIdentifier"); DERValue algIdVal = der.read(); algId = (OID) algIdVal.getValue(); - debug("read object identifier == " + algId); + if (Configuration.DEBUG) + log.fine("read object identifier == " + algId); if (val.getLength() > algIdVal.getEncodedLength()) { val = der.read(); - debug("read parameters len == " + val.getEncodedLength()); + if (Configuration.DEBUG) + log.fine("read parameters len == " + val.getEncodedLength()); algParams = val.getEncoded(); if (val.isConstructed()) in.skip(val.getLength()); @@ -394,18 +389,21 @@ public class X509CRL extends java.security.cert.X509CRL val = der.read(); issuerDN = new X500DistinguishedName(val.getEncoded()); der.skip(val.getLength()); - debug("read issuer == " + issuerDN); + if (Configuration.DEBUG) + log.fine("read issuer == " + issuerDN); // thisUpdate Time, thisUpdate = (Date) der.read().getValue(); - debug("read thisUpdate == " + thisUpdate); + if (Configuration.DEBUG) + log.fine("read thisUpdate == " + thisUpdate); // nextUpdate Time OPTIONAL, val = der.read(); if (val.getValue() instanceof Date) { nextUpdate = (Date) val.getValue(); - debug("read nextUpdate == " + nextUpdate); + if (Configuration.DEBUG) + log.fine("read nextUpdate == " + nextUpdate); val = der.read(); } @@ -433,7 +431,8 @@ public class X509CRL extends java.security.cert.X509CRL DERValue exts = der.read(); if (!exts.isConstructed()) throw new IOException("malformed Extensions"); - debug("start Extensions len == " + exts.getLength()); + if (Configuration.DEBUG) + log.fine("start Extensions len == " + exts.getLength()); int len = 0; while (len < exts.getLength()) { @@ -444,32 +443,42 @@ public class X509CRL extends java.security.cert.X509CRL extensions.put(e.getOid(), e); der.skip(ext.getLength()); len += ext.getEncodedLength(); - debug("current count == " + len); + if (Configuration.DEBUG) + log.fine("current count == " + len); } val = der.read(); } - debug("read tag == " + val.getTag()); + if (Configuration.DEBUG) + log.fine("read tag == " + val.getTag()); if (!val.isConstructed()) throw new IOException("malformed AlgorithmIdentifier"); - debug("start AlgorithmIdentifier len == " + val.getLength()); + if (Configuration.DEBUG) + log.fine("start AlgorithmIdentifier len == " + val.getLength()); DERValue sigAlgVal = der.read(); - debug("read tag == " + sigAlgVal.getTag()); + if (Configuration.DEBUG) + log.fine("read tag == " + sigAlgVal.getTag()); if (sigAlgVal.getTag() != DER.OBJECT_IDENTIFIER) throw new IOException("malformed AlgorithmIdentifier"); sigAlg = (OID) sigAlgVal.getValue(); - debug("signature id == " + sigAlg); - debug("sigAlgVal length == " + sigAlgVal.getEncodedLength()); + if (Configuration.DEBUG) + { + log.fine("signature id == " + sigAlg); + log.fine("sigAlgVal length == " + sigAlgVal.getEncodedLength()); + } if (val.getLength() > sigAlgVal.getEncodedLength()) { val = der.read(); - debug("sig params tag = " + val.getTag() + " len == " + val.getEncodedLength()); + if (Configuration.DEBUG) + log.fine("sig params tag = " + val.getTag() + " len == " + + val.getEncodedLength()); sigAlgParams = (byte[]) val.getEncoded(); if (val.isConstructed()) in.skip(val.getLength()); } val = der.read(); - debug("read tag = " + val.getTag()); + if (Configuration.DEBUG) + log.fine("read tag = " + val.getTag()); rawSig = val.getEncoded(); signature = ((BitString) val.getValue()).toByteArray(); } diff --git a/gnu/java/security/x509/X509CRLEntry.java b/gnu/java/security/x509/X509CRLEntry.java index a3bcfdea8..f3f3a93d6 100644 --- a/gnu/java/security/x509/X509CRLEntry.java +++ b/gnu/java/security/x509/X509CRLEntry.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.x509; +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.der.DERReader; import gnu.java.security.der.DERValue; @@ -53,6 +54,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.logging.Logger; /** * A single entry in a X.509 certificate revocation list. @@ -63,20 +65,7 @@ import java.util.Set; class X509CRLEntry extends java.security.cert.X509CRLEntry implements GnuPKIExtension { - - // Constants and fields. - // ------------------------------------------------------------------------ - - private static final boolean DEBUG = false; - private static void debug(String msg) - { - if (DEBUG) - { - System.err.print(">> X509CRLEntry: "); - System.err.println(msg); - } - } - + private static final Logger log = Logger.getLogger(X509CRLEntry.class.getName()); /** The DER encoded form of this CRL entry. */ private byte[] encoded; @@ -230,26 +219,29 @@ class X509CRLEntry extends java.security.cert.X509CRLEntry { // RevokedCertificate ::= SEQUENCE { DERValue entry = der.read(); - debug("start CRL entry len == " + entry.getLength()); + if (Configuration.DEBUG) + log.fine("start CRL entry len == " + entry.getLength()); if (!entry.isConstructed()) throw new IOException("malformed revokedCertificate"); encoded = entry.getEncoded(); int len = 0; - - debug("encoded entry:\n" + Util.hexDump(encoded, ">>>> ")); + if (Configuration.DEBUG) + log.fine("encoded entry:\n" + Util.hexDump(encoded, ">>>> ")); // userCertificate CertificateSerialNumber, DERValue val = der.read(); serialNo = (BigInteger) val.getValue(); len += val.getEncodedLength(); - debug("userCertificate == " + serialNo + " current count == " + len); + if (Configuration.DEBUG) + log.fine("userCertificate == " + serialNo + " current count == " + len); // revocationDate Time, val = der.read(); revocationDate = (Date) val.getValue(); len += val.getEncodedLength(); - debug("revocationDate == " + revocationDate + " current count == " + len); - + if (Configuration.DEBUG) + log.fine("revocationDate == " + revocationDate + " current count == " + + len); // crlEntryExtensions Extensions OPTIONAL // -- if present MUST be v2 if (len < entry.getLength()) @@ -259,19 +251,22 @@ class X509CRLEntry extends java.security.cert.X509CRLEntry DERValue exts = der.read(); if (!exts.isConstructed()) throw new IOException("malformed Extensions"); - debug("start Extensions len == " + exts.getLength()); + if (Configuration.DEBUG) + log.fine("start Extensions len == " + exts.getLength()); len = 0; while (len < exts.getLength()) { val = der.read(); if (!val.isConstructed()) throw new IOException("malformed Extension"); - debug("start Extension len == " + val.getLength()); + if (Configuration.DEBUG) + log.fine("start Extension len == " + val.getLength()); Extension e = new Extension(val.getEncoded()); extensions.put(e.getOid(), e); der.skip(val.getLength()); len += val.getEncodedLength(); - debug("current count == " + len); + if (Configuration.DEBUG) + log.fine("current count == " + len); } } } diff --git a/gnu/java/security/x509/ext/Extension.java b/gnu/java/security/x509/ext/Extension.java index 97097a2f3..4deafe23e 100644 --- a/gnu/java/security/x509/ext/Extension.java +++ b/gnu/java/security/x509/ext/Extension.java @@ -38,6 +38,7 @@ exception statement from your version. */ package gnu.java.security.x509.ext; +import gnu.classpath.Configuration; import gnu.java.security.OID; import gnu.java.security.der.DER; import gnu.java.security.der.DERReader; @@ -48,20 +49,11 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.logging.Logger; public class Extension { - - // Fields. - // ------------------------------------------------------------------------- - - private static final boolean DEBUG = false; - private static void debug(String msg) - { - System.err.print(">> Extension: "); - System.err.println(msg); - } - + private static final Logger log = Logger.getLogger(Extension.class.getName()); /** * This extension's object identifier. */ @@ -97,7 +89,8 @@ public class Extension // Extension ::= SEQUENCE { DERValue val = der.read(); - if (DEBUG) debug("read val tag == " + val.getTag() + " len == " + val.getLength()); + if (Configuration.DEBUG) + log.fine("read val tag == " + val.getTag() + " len == " + val.getLength()); if (!val.isConstructed()) throw new IOException("malformed Extension"); @@ -106,7 +99,8 @@ public class Extension if (val.getTag() != DER.OBJECT_IDENTIFIER) throw new IOException("expecting OBJECT IDENTIFIER"); oid = (OID) val.getValue(); - if (DEBUG) debug("read oid == " + oid); + if (Configuration.DEBUG) + log.fine("read oid == " + oid); // critical BOOLEAN DEFAULT FALSE, val = der.read(); @@ -117,7 +111,8 @@ public class Extension } else critical = false; - if (DEBUG) debug("is critical == " + critical); + if (Configuration.DEBUG) + log.fine("is critical == " + critical); // extnValue OCTET STRING } if (val.getTag() != DER.OCTET_STRING) @@ -181,7 +176,8 @@ public class Extension value = new Value(encval); isSupported = false; } - if (DEBUG) debug("read value == " + value); + if (Configuration.DEBUG) + log.fine("read value == " + value); } public Extension (final OID oid, final Value value, final boolean critical) diff --git a/gnu/java/security/x509/ext/GeneralNames.java b/gnu/java/security/x509/ext/GeneralNames.java index dae94cd9f..81c090f4c 100644 --- a/gnu/java/security/x509/ext/GeneralNames.java +++ b/gnu/java/security/x509/ext/GeneralNames.java @@ -1,5 +1,5 @@ /* GeneralNames.java -- the GeneralNames object - Copyright (C) 2004 Free Software Foundation, Inc. + Copyright (C) 2004, 2006 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -42,7 +42,6 @@ import gnu.java.security.OID; import gnu.java.security.der.DER; import gnu.java.security.der.DERReader; import gnu.java.security.der.DERValue; -import gnu.java.security.x509.X500DistinguishedName; import java.io.IOException; import java.net.InetAddress; diff --git a/gnu/java/util/regex/BacktrackStack.java b/gnu/java/util/regex/BacktrackStack.java new file mode 100644 index 000000000..d711945e4 --- /dev/null +++ b/gnu/java/util/regex/BacktrackStack.java @@ -0,0 +1,112 @@ +/* gnu/regexp/BacktrackStack.java + 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.util.regex; + +/** + * An instance of this class represents a stack + * used for backtracking. + * + * @author Ito Kazumitsu</A> + */ +final class BacktrackStack { + + /** A set of data to be used for backtracking. */ + static class Backtrack { + /** REToken to which to go back */ + REToken token; + /** CharIndexed on which matches are being searched for. */ + CharIndexed input; + /** REMatch to be used by the REToken token. */ + REMatch match; + /** Some parameter used by the token's backtrack method. */ + Object param; + Backtrack(REToken token, CharIndexed input, REMatch match, Object param) { + this.token = token; + this.input = input; + // REMatch may change before backtracking is needed. So we + // keep a clone of it. + this.match = (REMatch) match.clone(); + this.param = param; + } + } + + Backtrack[] stack; + private int size; + private int capacity; + private static final int INITIAL_CAPACITY = 32; + private static final int CAPACITY_INCREMENT = 16; + + BacktrackStack() { + stack = new Backtrack[INITIAL_CAPACITY]; + size = 0; + capacity = INITIAL_CAPACITY; + } + + boolean empty() { + return size == 0; + } + + Backtrack peek() { + return stack[size - 1]; + } + + Backtrack pop() { + Backtrack bt = stack[--size]; + stack[size] = null; + return bt; + } + + void clear() { + for (int i = 0; i < size; i++) { + stack[i] = null; + } + size = 0; + } + + void push(Backtrack bt) { + if (size >= capacity) { + capacity += CAPACITY_INCREMENT; + Backtrack[] newStack = new Backtrack[capacity]; + System.arraycopy(stack, 0, newStack, 0, size); + stack = newStack; + } + stack[size++] = bt; + } + +} diff --git a/gnu/java/util/regex/CharIndexed.java b/gnu/java/util/regex/CharIndexed.java new file mode 100644 index 000000000..6cd857e3b --- /dev/null +++ b/gnu/java/util/regex/CharIndexed.java @@ -0,0 +1,116 @@ +/* gnu/regexp/CharIndexed.java + Copyright (C) 1998-2001, 2004, 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.util.regex; + +/** + * Defines the interface used internally so that different types of source + * text can be accessed in the same way. Built-in concrete classes provide + * support for String, StringBuffer, InputStream and char[] types. + * A class that is CharIndexed supports the notion of a cursor within a + * block of text. The cursor must be able to be advanced via the move() + * method. The charAt() method returns the character at the cursor position + * plus a given offset. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ +public interface CharIndexed { + /** + * Defines a constant (0xFFFF was somewhat arbitrarily chosen) + * that can be returned by the charAt() function indicating that + * the specified index is out of range. + */ + char OUT_OF_BOUNDS = '\uFFFF'; + + /** + * Returns the character at the given offset past the current cursor + * position in the input. The index of the current position is zero. + * It is possible for this method to be called with a negative index. + * This happens when using the '^' operator in multiline matching mode + * or the '\b' or '\<' word boundary operators. In any case, the lower + * bound is currently fixed at -2 (for '^' with a two-character newline). + * + * @param index the offset position in the character field to examine + * @return the character at the specified index, or the OUT_OF_BOUNDS + * character defined by this interface. + */ + char charAt(int index); + + /** + * Shifts the input buffer by a given number of positions. Returns + * true if the new cursor position is valid. + */ + boolean move(int index); + + /** + * Returns true if the most recent move() operation placed the cursor + * position at a valid position in the input. + */ + boolean isValid(); + + /** + * Returns another CharIndexed containing length characters to the left + * of the given index. The given length is an expected maximum and + * the returned CharIndexed may not necessarily contain so many characters. + */ + CharIndexed lookBehind(int index, int length); + + /** + * Returns the effective length of this CharIndexed + */ + int length(); + + /** + * Sets the REMatch last found on this input. + */ + void setLastMatch(REMatch match); + + /** + * Returns the REMatch last found on this input. + */ + REMatch getLastMatch(); + + /** + * Returns the anchor. + */ + int getAnchor(); + + /** + * Sets the anchor. + */ + void setAnchor(int anchor); +} diff --git a/gnu/java/util/regex/CharIndexedCharArray.java b/gnu/java/util/regex/CharIndexedCharArray.java new file mode 100644 index 000000000..6f74c992f --- /dev/null +++ b/gnu/java/util/regex/CharIndexedCharArray.java @@ -0,0 +1,46 @@ +/* gnu/regexp/CharIndexedCharArray.java + 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.util.regex; +import java.nio.CharBuffer; + +class CharIndexedCharArray extends CharIndexedCharSequence { + + CharIndexedCharArray(char[] str, int index) { + super(CharBuffer.wrap(str), index); + } +} diff --git a/gnu/java/util/regex/CharIndexedCharSequence.java b/gnu/java/util/regex/CharIndexedCharSequence.java new file mode 100644 index 000000000..2eb753b0f --- /dev/null +++ b/gnu/java/util/regex/CharIndexedCharSequence.java @@ -0,0 +1,82 @@ +/* gnu/regexp/CharIndexedCharSequence.java + 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.util.regex; +import java.io.Serializable; + +class CharIndexedCharSequence implements CharIndexed, Serializable { + private CharSequence s; + private int anchor; + private int len; + + CharIndexedCharSequence(CharSequence s, int index) { + this.s = s; + len = s.length(); + anchor = index; + } + + public char charAt(int index) { + int pos = anchor + index; + return ((pos < len) && (pos >= 0)) ? s.charAt(pos) : OUT_OF_BOUNDS; + } + + public boolean isValid() { + return (anchor < len); + } + + public boolean move(int index) { + return ((anchor += index) < len); + } + + public CharIndexed lookBehind(int index, int length) { + if (length > (anchor + index)) length = anchor + index; + return new CharIndexedCharSequence(s, anchor + index - length); + } + + public int length() { + return len - anchor; + } + + private REMatch lastMatch; + public void setLastMatch(REMatch match) { + lastMatch = (REMatch)match.clone(); + lastMatch.anchor = anchor; + } + public REMatch getLastMatch() { return lastMatch; } + public int getAnchor() { return anchor; } + public void setAnchor(int anchor) { this.anchor = anchor; } +} diff --git a/gnu/java/util/regex/CharIndexedInputStream.java b/gnu/java/util/regex/CharIndexedInputStream.java new file mode 100644 index 000000000..77cd1abd5 --- /dev/null +++ b/gnu/java/util/regex/CharIndexedInputStream.java @@ -0,0 +1,181 @@ +/* gnu/regexp/CharIndexedInputStream.java + Copyright (C) 1998-2001, 2004, 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.util.regex; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +// TODO: move(x) shouldn't rely on calling next() x times + +class CharIndexedInputStream implements CharIndexed { + private static final int BUFFER_INCREMENT = 1024; + private static final int UNKNOWN = Integer.MAX_VALUE; // value for end + + private BufferedInputStream br; + + // so that we don't try to reset() right away + private int index = -1; + + private int bufsize = BUFFER_INCREMENT; + + private int end = UNKNOWN; + + private char cached = OUT_OF_BOUNDS; + + // Big enough for a \r\n pair + // lookBehind[0] = most recent + // lookBehind[1] = second most recent + private char[] lookBehind = new char[] { OUT_OF_BOUNDS, OUT_OF_BOUNDS }; + + CharIndexedInputStream(InputStream str, int index) { + if (str instanceof BufferedInputStream) br = (BufferedInputStream) str; + else br = new BufferedInputStream(str,BUFFER_INCREMENT); + next(); + if (index > 0) move(index); + } + + private boolean next() { + if (end == 1) return false; + end--; // closer to end + + try { + if (index != -1) { + br.reset(); + } + int i = br.read(); + br.mark(bufsize); + if (i == -1) { + end = 1; + cached = OUT_OF_BOUNDS; + return false; + } + cached = (char) i; + index = 1; + } catch (IOException e) { + e.printStackTrace(); + cached = OUT_OF_BOUNDS; + return false; + } + return true; + } + + public char charAt(int index) { + if (index == 0) { + return cached; + } else if (index >= end) { + return OUT_OF_BOUNDS; + } else if (index == -1) { + return lookBehind[0]; + } else if (index == -2) { + return lookBehind[1]; + } else if (index < -2) { + return OUT_OF_BOUNDS; + } else if (index >= bufsize) { + // Allocate more space in the buffer. + try { + while (bufsize <= index) bufsize += BUFFER_INCREMENT; + br.reset(); + br.mark(bufsize); + br.skip(index-1); + } catch (IOException e) { } + } else if (this.index != index) { + try { + br.reset(); + br.skip(index-1); + } catch (IOException e) { } + } + char ch = OUT_OF_BOUNDS; + + try { + int i = br.read(); + this.index = index+1; // this.index is index of next pos relative to charAt(0) + if (i == -1) { + // set flag that next should fail next time? + end = index; + return ch; + } + ch = (char) i; + } catch (IOException ie) { } + + return ch; + } + + public boolean move(int index) { + // move read position [index] clicks from 'charAt(0)' + boolean retval = true; + while (retval && (index-- > 0)) retval = next(); + return retval; + } + + public boolean isValid() { + return (cached != OUT_OF_BOUNDS); + } + + public CharIndexed lookBehind(int index, int length) { + throw new UnsupportedOperationException( + "difficult to look behind for an input stream"); + } + + public int length() { + throw new UnsupportedOperationException( + "difficult to tell the length for an input stream"); + } + + public void setLastMatch(REMatch match) { + throw new UnsupportedOperationException( + "difficult to support setLastMatch for an input stream"); + } + + public REMatch getLastMatch() { + throw new UnsupportedOperationException( + "difficult to support getLastMatch for an input stream"); + } + + public int getAnchor() { + throw new UnsupportedOperationException( + "difficult to support getAnchor for an input stream"); + } + + public void setAnchor(int anchor) { + throw new UnsupportedOperationException( + "difficult to support setAnchor for an input stream"); + } + + +} + diff --git a/gnu/java/util/regex/CharIndexedString.java b/gnu/java/util/regex/CharIndexedString.java new file mode 100644 index 000000000..fab6d7836 --- /dev/null +++ b/gnu/java/util/regex/CharIndexedString.java @@ -0,0 +1,44 @@ +/* gnu/regexp/CharIndexedString.java + 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.util.regex; + +class CharIndexedString extends CharIndexedCharSequence { + CharIndexedString(String str, int index) { + super(str, index); + } +} diff --git a/gnu/java/util/regex/CharIndexedStringBuffer.java b/gnu/java/util/regex/CharIndexedStringBuffer.java new file mode 100644 index 000000000..10005b668 --- /dev/null +++ b/gnu/java/util/regex/CharIndexedStringBuffer.java @@ -0,0 +1,45 @@ +/* gnu/regexp/CharIndexedStringBuffer.java + 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.util.regex; + +class CharIndexedStringBuffer extends CharIndexedCharSequence { + + CharIndexedStringBuffer(StringBuffer str, int index) { + super(str, index); + } +} diff --git a/gnu/java/util/regex/RE.java b/gnu/java/util/regex/RE.java new file mode 100644 index 000000000..cd12aab8f --- /dev/null +++ b/gnu/java/util/regex/RE.java @@ -0,0 +1,2102 @@ +/* gnu/regexp/RE.java + 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.util.regex; +import java.io.InputStream; +import java.io.Serializable; +import java.util.Locale; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; +import java.util.Stack; +import java.util.Vector; + +/** + * RE provides the user interface for compiling and matching regular + * expressions. + * <P> + * A regular expression object (class RE) is compiled by constructing it + * from a String, StringBuffer or character array, with optional + * compilation flags (below) + * and an optional syntax specification (see RESyntax; if not specified, + * <code>RESyntax.RE_SYNTAX_PERL5</code> is used). + * <P> + * Once compiled, a regular expression object is reusable as well as + * threadsafe: multiple threads can use the RE instance simultaneously + * to match against different input text. + * <P> + * Various methods attempt to match input text against a compiled + * regular expression. These methods are: + * <LI><code>isMatch</code>: returns true if the input text in its + * entirety matches the regular expression pattern. + * <LI><code>getMatch</code>: returns the first match found in the + * input text, or null if no match is found. + * <LI><code>getAllMatches</code>: returns an array of all + * non-overlapping matches found in the input text. If no matches are + * found, the array is zero-length. + * <LI><code>substitute</code>: substitute the first occurence of the + * pattern in the input text with a replacement string (which may + * include metacharacters $0-$9, see REMatch.substituteInto). + * <LI><code>substituteAll</code>: same as above, but repeat for each + * match before returning. + * <LI><code>getMatchEnumeration</code>: returns an REMatchEnumeration + * object that allows iteration over the matches (see + * REMatchEnumeration for some reasons why you may want to do this + * instead of using <code>getAllMatches</code>. + * <P> + * + * These methods all have similar argument lists. The input can be a + * CharIndexed, String, a character array, a StringBuffer, or an + * InputStream of some sort. Note that when using an + * InputStream, the stream read position cannot be guaranteed after + * attempting a match (this is not a bug, but a consequence of the way + * regular expressions work). Using an REMatchEnumeration can + * eliminate most positioning problems. + * + * Although the input object can be of various types, it is recommended + * that it should be a CharIndexed because {@link CharIndexed#getLastMatch()} + * can show the last match found on this input, which helps the expression + * \G work as the end of the previous match. + * + * <P> + * + * The optional index argument specifies the offset from the beginning + * of the text at which the search should start (see the descriptions + * of some of the execution flags for how this can affect positional + * pattern operators). For an InputStream, this means an + * offset from the current read position, so subsequent calls with the + * same index argument on an InputStream will not + * necessarily access the same position on the stream, whereas + * repeated searches at a given index in a fixed string will return + * consistent results. + * + * <P> + * You can optionally affect the execution environment by using a + * combination of execution flags (constants listed below). + * + * <P> + * All operations on a regular expression are performed in a + * thread-safe manner. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + * @version 1.1.5-dev, to be released + */ + +public class RE extends REToken { + + private static final class IntPair implements Serializable { + public int first, second; + } + + private static final class CharUnit implements Serializable { + public char ch; + public boolean bk; + } + + // This String will be returned by getVersion() + 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/regexp/MessagesBundle", Locale.getDefault()); + + // These are, respectively, the first and last tokens in our linked list + // If there is only one token, firstToken == lastToken + private REToken firstToken, lastToken; + + // This is the number of subexpressions in this regular expression, + // with a minimum value of zero. Returned by getNumSubs() + private int numSubs; + + /** Minimum length, in characters, of any possible match. */ + private int minimumLength; + private int maximumLength; + + /** + * Compilation flag. Do not differentiate case. Subsequent + * searches using this RE will be case insensitive. + */ + public static final int REG_ICASE = 0x02; + + /** + * Compilation flag. The match-any-character operator (dot) + * will match a newline character. When set this overrides the syntax + * bit RE_DOT_NEWLINE (see RESyntax for details). This is equivalent to + * the "/s" operator in Perl. + */ + public static final int REG_DOT_NEWLINE = 0x04; + + /** + * Compilation flag. Use multiline mode. In this mode, the ^ and $ + * anchors will match based on newlines within the input. This is + * equivalent to the "/m" operator in Perl. + */ + public static final int REG_MULTILINE = 0x08; + + /** + * Execution flag. + * The match-beginning operator (^) will not match at the beginning + * of the input string. Useful for matching on a substring when you + * know the context of the input is such that position zero of the + * input to the match test is not actually position zero of the text. + * <P> + * This example demonstrates the results of various ways of matching on + * a substring. + * <P> + * <CODE> + * String s = "food bar fool";<BR> + * RE exp = new RE("^foo.");<BR> + * REMatch m0 = exp.getMatch(s);<BR> + * REMatch m1 = exp.getMatch(s.substring(8));<BR> + * REMatch m2 = exp.getMatch(s.substring(8),0,RE.REG_NOTBOL); <BR> + * REMatch m3 = exp.getMatch(s,8); <BR> + * REMatch m4 = exp.getMatch(s,8,RE.REG_ANCHORINDEX); <BR> + * <P> + * // Results:<BR> + * // m0.toString(): "food"<BR> + * // m1.toString(): "fool"<BR> + * // m2.toString(): null<BR> + * // m3.toString(): null<BR> + * // m4.toString(): "fool"<BR> + * </CODE> + */ + public static final int REG_NOTBOL = 0x10; + + /** + * Execution flag. + * The match-end operator ($) does not match at the end + * of the input string. Useful for matching on substrings. + */ + public static final int REG_NOTEOL = 0x20; + + /** + * Execution flag. + * When a match method is invoked that starts matching at a non-zero + * index into the input, treat the input as if it begins at the index + * given. The effect of this flag is that the engine does not "see" + * any text in the input before the given index. This is useful so + * that the match-beginning operator (^) matches not at position 0 + * in the input string, but at the position the search started at + * (based on the index input given to the getMatch function). See + * the example under REG_NOTBOL. It also affects the use of the \< + * and \b operators. + */ + public static final int REG_ANCHORINDEX = 0x40; + + /** + * Execution flag. + * The substitute and substituteAll methods will not attempt to + * interpolate occurrences of $1-$9 in the replacement text with + * the corresponding subexpressions. For example, you may want to + * replace all matches of "one dollar" with "$1". + */ + public static final int REG_NO_INTERPOLATE = 0x80; + + /** + * Execution flag. + * Try to match the whole input string. An implicit match-end operator + * is added to this regexp. + */ + public static final int REG_TRY_ENTIRE_MATCH = 0x0100; + + /** + * Execution flag. + * The substitute and substituteAll methods will treat the + * character '\' in the replacement as an escape to a literal + * character. In this case "\n", "\$", "\\", "\x40" and "\012" + * will become "n", "$", "\", "x40" and "012" respectively. + * This flag has no effect if REG_NO_INTERPOLATE is set on. + */ + public static final int REG_REPLACE_USE_BACKSLASHESCAPE = 0x0200; + + /** + * Compilation flag. Allow whitespace and comments in pattern. + * This is equivalent to the "/x" operator in Perl. + */ + public static final int REG_X_COMMENTS = 0x0400; + + /** + * Compilation flag. If set, REG_ICASE is effective only for US-ASCII. + */ + public static final int REG_ICASE_USASCII = 0x0800; + + /** Returns a string representing the version of the gnu.regexp package. */ + public static final String version() { + return VERSION; + } + + // Retrieves a message from the ResourceBundle + static final String getLocalizedMessage(String key) { + return messages.getString(key); + } + + /** + * Constructs a regular expression pattern buffer without any compilation + * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer or char[]. Other input types will be converted to + * strings using the toString() method. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE(Object pattern) throws REException { + this(pattern,0,RESyntax.RE_SYNTAX_PERL5,0,0); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags listed above. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE(Object pattern, int cflags) throws REException { + this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5,0,0); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and regular expression syntax. + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags listed above. + * @param syntax The type of regular expression syntax to use. + * @exception REException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public RE(Object pattern, int cflags, RESyntax syntax) throws REException { + this(pattern,cflags,syntax,0,0); + } + + // internal constructor used for alternation + private RE(REToken first, REToken last,int subs, int subIndex, int minLength, int maxLength) { + super(subIndex); + firstToken = first; + lastToken = last; + numSubs = subs; + minimumLength = minLength; + maximumLength = maxLength; + addToken(new RETokenEndSub(subIndex)); + } + + private RE(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException { + super(myIndex); // Subexpression index of this token. + initialize(patternObj, cflags, syntax, myIndex, nextSub); + } + + // For use by subclasses + protected RE() { super(0); } + + // The meat of construction + protected void initialize(Object patternObj, int cflags, RESyntax syntax, int myIndex, int nextSub) throws REException { + char[] pattern; + if (patternObj instanceof String) { + pattern = ((String) patternObj).toCharArray(); + } else if (patternObj instanceof char[]) { + pattern = (char[]) patternObj; + } else if (patternObj instanceof StringBuffer) { + pattern = new char [((StringBuffer) patternObj).length()]; + ((StringBuffer) patternObj).getChars(0,pattern.length,pattern,0); + } else { + pattern = patternObj.toString().toCharArray(); + } + + int pLength = pattern.length; + + numSubs = 0; // Number of subexpressions in this token. + Vector branches = null; + + // linked list of tokens (sort of -- some closed loops can exist) + firstToken = lastToken = null; + + // Precalculate these so we don't pay for the math every time we + // need to access them. + boolean insens = ((cflags & REG_ICASE) > 0); + boolean insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + + // Parse pattern into tokens. Does anyone know if it's more efficient + // to use char[] than a String.charAt()? I'm assuming so. + + // index tracks the position in the char array + int index = 0; + + // this will be the current parse character (pattern[index]) + CharUnit unit = new CharUnit(); + + // This is used for {x,y} calculations + IntPair minMax = new IntPair(); + + // Buffer a token so we can create a TokenRepeated, etc. + REToken currentToken = null; + char ch; + boolean quot = false; + + // Saved syntax and flags. + RESyntax savedSyntax = null; + int savedCflags = 0; + boolean flagsSaved = false; + + while (index < pLength) { + // read the next character unit (including backslash escapes) + index = getCharUnit(pattern,index,unit,quot); + + if (unit.bk) + if (unit.ch == 'Q') { + quot = true; + continue; + } else if (unit.ch == 'E') { + quot = false; + continue; + } + if (quot) + unit.bk = false; + + if (((cflags & REG_X_COMMENTS) > 0) && (!unit.bk) && (!quot)) { + if (Character.isWhitespace(unit.ch)) { + continue; + } + if (unit.ch == '#') { + for (int i = index; i < pLength; i++) { + if (pattern[i] == '\n') { + index = i + 1; + continue; + } + else if (pattern[i] == '\r') { + if (i + 1 < pLength && pattern[i + 1] == '\n') { + index = i + 2; + } + else { + index = i + 1; + } + continue; + } + } + index = pLength; + continue; + } + } + + // ALTERNATION OPERATOR + // \| or | (if RE_NO_BK_VBAR) or newline (if RE_NEWLINE_ALT) + // not available if RE_LIMITED_OPS is set + + // TODO: the '\n' literal here should be a test against REToken.newline, + // which unfortunately may be more than a single character. + if ( ( (unit.ch == '|' && (syntax.get(RESyntax.RE_NO_BK_VBAR) ^ (unit.bk || quot))) + || (syntax.get(RESyntax.RE_NEWLINE_ALT) && (unit.ch == '\n') && !(unit.bk || quot)) ) + && !syntax.get(RESyntax.RE_LIMITED_OPS)) { + // make everything up to here be a branch. create vector if nec. + addToken(currentToken); + RE theBranch = new RE(firstToken, lastToken, numSubs, subIndex, minimumLength, maximumLength); + minimumLength = 0; + maximumLength = 0; + if (branches == null) { + branches = new Vector(); + } + branches.addElement(theBranch); + firstToken = lastToken = currentToken = null; + } + + // INTERVAL OPERATOR: + // {x} | {x,} | {x,y} (RE_INTERVALS && RE_NO_BK_BRACES) + // \{x\} | \{x,\} | \{x,y\} (RE_INTERVALS && !RE_NO_BK_BRACES) + // + // OPEN QUESTION: + // what is proper interpretation of '{' at start of string? + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*){2,}", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '{') && syntax.get(RESyntax.RE_INTERVALS) && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ (unit.bk || quot))) { + int newIndex = getMinMax(pattern,index,minMax,syntax); + if (newIndex > index) { + if (minMax.first > minMax.second) + throw new REException(getLocalizedMessage("interval.order"),REException.REG_BADRPT,newIndex); + if (currentToken == null) + throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,newIndex); + if (currentToken instanceof RETokenRepeated) + throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,newIndex); + if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) + throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,newIndex); + index = newIndex; + currentToken = setRepeated(currentToken,minMax.first,minMax.second,index); + } + else { + addToken(currentToken); + currentToken = new RETokenChar(subIndex,unit.ch,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + } + } + + // LIST OPERATOR: + // [...] | [^...] + + else if ((unit.ch == '[') && !(unit.bk || quot)) { + // Create a new RETokenOneOf + ParseCharClassResult result = parseCharClass( + subIndex, pattern, index, pLength, cflags, syntax, 0); + addToken(currentToken); + currentToken = result.token; + index = result.index; + } + + // SUBEXPRESSIONS + // (...) | \(...\) depending on RE_NO_BK_PARENS + + else if ((unit.ch == '(') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) { + boolean pure = false; + boolean comment = false; + boolean lookAhead = false; + boolean lookBehind = false; + boolean independent = false; + boolean negativelh = false; + boolean negativelb = false; + if ((index+1 < pLength) && (pattern[index] == '?')) { + switch (pattern[index+1]) { + case '!': + if (syntax.get(RESyntax.RE_LOOKAHEAD)) { + pure = true; + negativelh = true; + lookAhead = true; + index += 2; + } + break; + case '=': + if (syntax.get(RESyntax.RE_LOOKAHEAD)) { + pure = true; + lookAhead = true; + index += 2; + } + break; + case '<': + // We assume that if the syntax supports look-ahead, + // it also supports look-behind. + if (syntax.get(RESyntax.RE_LOOKAHEAD)) { + index++; + switch (pattern[index +1]) { + case '!': + pure = true; + negativelb = true; + lookBehind = true; + index += 2; + break; + case '=': + pure = true; + lookBehind = true; + index += 2; + } + } + break; + case '>': + // We assume that if the syntax supports look-ahead, + // it also supports independent group. + if (syntax.get(RESyntax.RE_LOOKAHEAD)) { + pure = true; + independent = true; + index += 2; + } + break; + case 'i': + case 'd': + case 'm': + case 's': + case 'u': + case 'x': + case '-': + if (!syntax.get(RESyntax.RE_EMBEDDED_FLAGS)) break; + // Set or reset syntax flags. + int flagIndex = index + 1; + int endFlag = -1; + RESyntax newSyntax = new RESyntax(syntax); + int newCflags = cflags; + boolean negate = false; + while (flagIndex < pLength && endFlag < 0) { + switch(pattern[flagIndex]) { + case 'i': + if (negate) + newCflags &= ~REG_ICASE; + else + newCflags |= REG_ICASE; + flagIndex++; + break; + case 'd': + if (negate) + newSyntax.setLineSeparator(RESyntax.DEFAULT_LINE_SEPARATOR); + else + newSyntax.setLineSeparator("\n"); + flagIndex++; + break; + case 'm': + if (negate) + newCflags &= ~REG_MULTILINE; + else + newCflags |= REG_MULTILINE; + flagIndex++; + break; + case 's': + if (negate) + newCflags &= ~REG_DOT_NEWLINE; + else + newCflags |= REG_DOT_NEWLINE; + flagIndex++; + break; + case 'u': + if (negate) + newCflags |= REG_ICASE_USASCII; + else + newCflags &= ~REG_ICASE_USASCII; + flagIndex++; + break; + case 'x': + if (negate) + newCflags &= ~REG_X_COMMENTS; + else + newCflags |= REG_X_COMMENTS; + flagIndex++; + break; + case '-': + negate = true; + flagIndex++; + break; + case ':': + case ')': + endFlag = pattern[flagIndex]; + break; + default: + throw new REException(getLocalizedMessage("repeat.no.token"), REException.REG_BADRPT, index); + } + } + if (endFlag == ')') { + syntax = newSyntax; + cflags = newCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + // This can be treated as though it were a comment. + comment = true; + index = flagIndex - 1; + break; + } + if (endFlag == ':') { + savedSyntax = syntax; + savedCflags = cflags; + flagsSaved = true; + syntax = newSyntax; + cflags = newCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + index = flagIndex -1; + // Fall through to the next case. + } + else { + throw new REException(getLocalizedMessage("unmatched.paren"), REException.REG_ESUBREG,index); + } + case ':': + if (syntax.get(RESyntax.RE_PURE_GROUPING)) { + pure = true; + index += 2; + } + break; + case '#': + if (syntax.get(RESyntax.RE_COMMENTS)) { + comment = true; + } + break; + default: + throw new REException(getLocalizedMessage("repeat.no.token"), REException.REG_BADRPT, index); + } + } + + if (index >= pLength) { + throw new REException(getLocalizedMessage("unmatched.paren"), REException.REG_ESUBREG,index); + } + + // find end of subexpression + int endIndex = index; + int nextIndex = index; + int nested = 0; + + while ( ((nextIndex = getCharUnit(pattern,endIndex,unit,false)) > 0) + && !(nested == 0 && (unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) ) { + if ((endIndex = nextIndex) >= pLength) + throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); + else if ((unit.ch == '[') && !(unit.bk || quot)) { + // I hate to do something similar to the LIST OPERATOR matters + // above, but ... + int listIndex = nextIndex; + if (listIndex < pLength && pattern[listIndex] == '^') listIndex++; + if (listIndex < pLength && pattern[listIndex] == ']') listIndex++; + int listEndIndex = -1; + int listNest = 0; + while (listIndex < pLength && listEndIndex < 0) { + switch(pattern[listIndex++]) { + case '\\': + listIndex++; + break; + case '[': + // Sun's API document says that regexp like "[a-d[m-p]]" + // is legal. Even something like "[[[^]]]]" is accepted. + listNest++; + if (listIndex < pLength && pattern[listIndex] == '^') listIndex++; + if (listIndex < pLength && pattern[listIndex] == ']') listIndex++; + break; + case ']': + if (listNest == 0) + listEndIndex = listIndex; + listNest--; + break; + } + } + if (listEndIndex >= 0) { + nextIndex = listEndIndex; + if ((endIndex = nextIndex) >= pLength) + throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); + else + continue; + } + throw new REException(getLocalizedMessage("subexpr.no.end"),REException.REG_ESUBREG,nextIndex); + } + else if (unit.ch == '(' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) + nested++; + else if (unit.ch == ')' && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot))) + nested--; + } + + // endIndex is now position at a ')','\)' + // nextIndex is end of string or position after ')' or '\)' + + if (comment) index = nextIndex; + else { // not a comment + // create RE subexpression as token. + addToken(currentToken); + if (!pure) { + numSubs++; + } + + int useIndex = (pure || lookAhead || lookBehind || independent) ? + 0 : nextSub + numSubs; + currentToken = new RE(String.valueOf(pattern,index,endIndex-index).toCharArray(),cflags,syntax,useIndex,nextSub + numSubs); + numSubs += ((RE) currentToken).getNumSubs(); + + if (lookAhead) { + currentToken = new RETokenLookAhead(currentToken,negativelh); + } + else if (lookBehind) { + currentToken = new RETokenLookBehind(currentToken,negativelb); + } + else if (independent) { + currentToken = new RETokenIndependent(currentToken); + } + + index = nextIndex; + if (flagsSaved) { + syntax = savedSyntax; + cflags = savedCflags; + insens = ((cflags & REG_ICASE) > 0); + insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + flagsSaved = false; + } + } // not a comment + } // subexpression + + // UNMATCHED RIGHT PAREN + // ) or \) throw exception if + // !syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) + else if (!syntax.get(RESyntax.RE_UNMATCHED_RIGHT_PAREN_ORD) && ((unit.ch == ')') && (syntax.get(RESyntax.RE_NO_BK_PARENS) ^ (unit.bk || quot)))) { + throw new REException(getLocalizedMessage("unmatched.paren"),REException.REG_EPAREN,index); + } + + // START OF LINE OPERATOR + // ^ + + else if ((unit.ch == '^') && !(unit.bk || quot)) { + addToken(currentToken); + currentToken = null; + addToken(new RETokenStart(subIndex,((cflags & REG_MULTILINE) > 0) ? syntax.getLineSeparator() : null)); + } + + // END OF LINE OPERATOR + // $ + + else if ((unit.ch == '$') && !(unit.bk || quot)) { + addToken(currentToken); + currentToken = null; + addToken(new RETokenEnd(subIndex,((cflags & REG_MULTILINE) > 0) ? syntax.getLineSeparator() : null)); + } + + // MATCH-ANY-CHARACTER OPERATOR (except possibly newline and null) + // . + + else if ((unit.ch == '.') && !(unit.bk || quot)) { + addToken(currentToken); + currentToken = new RETokenAny(subIndex,syntax.get(RESyntax.RE_DOT_NEWLINE) || ((cflags & REG_DOT_NEWLINE) > 0),syntax.get(RESyntax.RE_DOT_NOT_NULL)); + } + + // ZERO-OR-MORE REPEAT OPERATOR + // * + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*)*", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '*') && !(unit.bk || quot)) { + if (currentToken == null) + throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); + if (currentToken instanceof RETokenRepeated) + throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); + if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) + throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); + currentToken = setRepeated(currentToken,0,Integer.MAX_VALUE,index); + } + + // ONE-OR-MORE REPEAT OPERATOR / POSSESSIVE MATCHING OPERATOR + // + | \+ depending on RE_BK_PLUS_QM + // not available if RE_LIMITED_OPS is set + // + // This method used to check "repeat.empty.token" to avoid such regexp + // as "(a*)+", but now "repeat.empty.token" is allowed. + + else if ((unit.ch == '+') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ (unit.bk || quot))) { + if (currentToken == null) + throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); + + // Check for possessive matching on RETokenRepeated + if (currentToken instanceof RETokenRepeated) { + RETokenRepeated tokenRep = (RETokenRepeated)currentToken; + if (syntax.get(RESyntax.RE_POSSESSIVE_OPS) && !tokenRep.isPossessive() && !tokenRep.isStingy()) + tokenRep.makePossessive(); + else + throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); + + } + else if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) + throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); + else + currentToken = setRepeated(currentToken,1,Integer.MAX_VALUE,index); + } + + // ZERO-OR-ONE REPEAT OPERATOR / STINGY MATCHING OPERATOR + // ? | \? depending on RE_BK_PLUS_QM + // not available if RE_LIMITED_OPS is set + // stingy matching if RE_STINGY_OPS is set and it follows a quantifier + + else if ((unit.ch == '?') && !syntax.get(RESyntax.RE_LIMITED_OPS) && (!syntax.get(RESyntax.RE_BK_PLUS_QM) ^ (unit.bk || quot))) { + if (currentToken == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); + + // Check for stingy matching on RETokenRepeated + if (currentToken instanceof RETokenRepeated) { + RETokenRepeated tokenRep = (RETokenRepeated)currentToken; + if (syntax.get(RESyntax.RE_STINGY_OPS) && !tokenRep.isStingy() && !tokenRep.isPossessive()) + tokenRep.makeStingy(); + else + throw new REException(getLocalizedMessage("repeat.chained"),REException.REG_BADRPT,index); + } + else if (currentToken instanceof RETokenWordBoundary || currentToken instanceof RETokenWordBoundary) + throw new REException(getLocalizedMessage("repeat.assertion"),REException.REG_BADRPT,index); + else + currentToken = setRepeated(currentToken,0,1,index); + } + + // OCTAL CHARACTER + // \0377 + + else if (unit.bk && (unit.ch == '0') && syntax.get(RESyntax.RE_OCTAL_CHAR)) { + CharExpression ce = getCharExpression(pattern, index - 2, pLength, syntax); + if (ce == null) + throw new REException("invalid octal character", REException.REG_ESCAPE, index); + index = index - 2 + ce.len; + addToken(currentToken); + currentToken = new RETokenChar(subIndex,ce.ch,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // BACKREFERENCE OPERATOR + // \1 \2 ... \9 and \10 \11 \12 ... + // not available if RE_NO_BK_REFS is set + // Perl recognizes \10, \11, and so on only if enough number of + // parentheses have opened before it, otherwise they are treated + // as aliases of \010, \011, ... (octal characters). In case of + // Sun's JDK, octal character expression must always begin with \0. + // We will do as JDK does. But FIXME, take a look at "(a)(b)\29". + // JDK treats \2 as a back reference to the 2nd group because + // there are only two groups. But in our poor implementation, + // we cannot help but treat \29 as a back reference to the 29th group. + + else if (unit.bk && Character.isDigit(unit.ch) && !syntax.get(RESyntax.RE_NO_BK_REFS)) { + addToken(currentToken); + int numBegin = index - 1; + int numEnd = pLength; + for (int i = index; i < pLength; i++) { + if (! Character.isDigit(pattern[i])) { + numEnd = i; + break; + } + } + int num = parseInt(pattern, numBegin, numEnd-numBegin, 10); + + currentToken = new RETokenBackRef(subIndex,num,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + index = numEnd; + } + + // START OF STRING OPERATOR + // \A if RE_STRING_ANCHORS is set + + else if (unit.bk && (unit.ch == 'A') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenStart(subIndex,null); + } + + // WORD BREAK OPERATOR + // \b if ???? + + else if (unit.bk && (unit.ch == 'b') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, false); + } + + // WORD BEGIN OPERATOR + // \< if ???? + else if (unit.bk && (unit.ch == '<')) { + addToken(currentToken); + currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN, false); + } + + // WORD END OPERATOR + // \> if ???? + else if (unit.bk && (unit.ch == '>')) { + addToken(currentToken); + currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.END, false); + } + + // NON-WORD BREAK OPERATOR + // \B if ???? + + else if (unit.bk && (unit.ch == 'B') && syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenWordBoundary(subIndex, RETokenWordBoundary.BEGIN | RETokenWordBoundary.END, true); + } + + + // DIGIT OPERATOR + // \d if RE_CHAR_CLASS_ESCAPES is set + + else if (unit.bk && (unit.ch == 'd') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,false); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NON-DIGIT OPERATOR + // \D + + else if (unit.bk && (unit.ch == 'D') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.DIGIT,insens,true); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NEWLINE ESCAPE + // \n + + else if (unit.bk && (unit.ch == 'n')) { + addToken(currentToken); + currentToken = new RETokenChar(subIndex,'\n',false); + } + + // RETURN ESCAPE + // \r + + else if (unit.bk && (unit.ch == 'r')) { + addToken(currentToken); + currentToken = new RETokenChar(subIndex,'\r',false); + } + + // WHITESPACE OPERATOR + // \s if RE_CHAR_CLASS_ESCAPES is set + + else if (unit.bk && (unit.ch == 's') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,false); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NON-WHITESPACE OPERATOR + // \S + + else if (unit.bk && (unit.ch == 'S') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.SPACE,insens,true); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // TAB ESCAPE + // \t + + else if (unit.bk && (unit.ch == 't')) { + addToken(currentToken); + currentToken = new RETokenChar(subIndex,'\t',false); + } + + // ALPHANUMERIC OPERATOR + // \w + + else if (unit.bk && (unit.ch == 'w') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,false); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NON-ALPHANUMERIC OPERATOR + // \W + + else if (unit.bk && (unit.ch == 'W') && syntax.get(RESyntax.RE_CHAR_CLASS_ESCAPES)) { + addToken(currentToken); + currentToken = new RETokenPOSIX(subIndex,RETokenPOSIX.ALNUM,insens,true); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // END OF STRING OPERATOR + // \Z, \z + + // FIXME: \Z and \z are different in that if the input string + // ends with a line terminator, \Z matches the position before + // the final terminator. This special behavior of \Z is yet + // to be implemented. + + else if (unit.bk && (unit.ch == 'Z' || unit.ch == 'z') && + syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenEnd(subIndex,null); + } + + // HEX CHARACTER, UNICODE CHARACTER + // \x1B, \u1234 + + else if ((unit.bk && (unit.ch == 'x') && syntax.get(RESyntax.RE_HEX_CHAR)) || + (unit.bk && (unit.ch == 'u') && syntax.get(RESyntax.RE_UNICODE_CHAR))) { + CharExpression ce = getCharExpression(pattern, index - 2, pLength, syntax); + if (ce == null) + throw new REException("invalid hex character", REException.REG_ESCAPE, index); + index = index - 2 + ce.len; + addToken(currentToken); + currentToken = new RETokenChar(subIndex,ce.ch,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // NAMED PROPERTY + // \p{prop}, \P{prop} + + else if ((unit.bk && (unit.ch == 'p') && syntax.get(RESyntax.RE_NAMED_PROPERTY)) || + (unit.bk && (unit.ch == 'P') && syntax.get(RESyntax.RE_NAMED_PROPERTY))) { + NamedProperty np = getNamedProperty(pattern, index - 2, pLength); + if (np == null) + throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); + index = index - 2 + np.len; + addToken(currentToken); + currentToken = getRETokenNamedProperty(subIndex,np,insens,index); + if (insensUSASCII) currentToken.unicodeAware = false; + } + + // END OF PREVIOUS MATCH + // \G + + else if (unit.bk && (unit.ch == 'G') && + syntax.get(RESyntax.RE_STRING_ANCHORS)) { + addToken(currentToken); + currentToken = new RETokenEndOfPreviousMatch(subIndex); + } + + // NON-SPECIAL CHARACTER (or escape to make literal) + // c | \* for example + + else { // not a special character + addToken(currentToken); + currentToken = new RETokenChar(subIndex,unit.ch,insens); + if (insensUSASCII) currentToken.unicodeAware = false; + } + } // end while + + // Add final buffered token and an EndSub marker + addToken(currentToken); + + if (branches != null) { + branches.addElement(new RE(firstToken,lastToken,numSubs,subIndex,minimumLength, maximumLength)); + branches.trimToSize(); // compact the Vector + minimumLength = 0; + maximumLength = 0; + firstToken = lastToken = null; + addToken(new RETokenOneOf(subIndex,branches,false)); + } + else addToken(new RETokenEndSub(subIndex)); + + } + + private static class ParseCharClassResult { + RETokenOneOf token; + int index; + boolean returnAtAndOperator = false; + } + + /** + * Parse [...] or [^...] and make an RETokenOneOf instance. + * @param subIndex subIndex to be given to the created RETokenOneOf instance. + * @param pattern Input array of characters to be parsed. + * @param index Index pointing to the character next to the beginning '['. + * @param pLength Limit of the input array. + * @param cflags Compilation flags used to parse the pattern. + * @param pflags Flags that affect the behavior of this method. + * @param syntax Syntax used to parse the pattern. + */ + private static ParseCharClassResult parseCharClass(int subIndex, + char[] pattern, int index, + int pLength, int cflags, RESyntax syntax, int pflags) + throws REException { + + boolean insens = ((cflags & REG_ICASE) > 0); + boolean insensUSASCII = ((cflags & REG_ICASE_USASCII) > 0); + Vector options = new Vector(); + Vector addition = new Vector(); + boolean additionAndAppeared = false; + final int RETURN_AT_AND = 0x01; + boolean returnAtAndOperator = ((pflags & RETURN_AT_AND) != 0); + boolean negative = false; + char ch; + + char lastChar = 0; + boolean lastCharIsSet = false; + if (index == pLength) throw new REException(getLocalizedMessage("unmatched.bracket"),REException.REG_EBRACK,index); + + // Check for initial caret, negation + if ((ch = pattern[index]) == '^') { + negative = true; + if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + ch = pattern[index]; + } + + // Check for leading right bracket literal + if (ch == ']') { + lastChar = ch; lastCharIsSet = true; + if (++index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + } + + while ((ch = pattern[index++]) != ']') { + if ((ch == '-') && (lastCharIsSet)) { + if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + if ((ch = pattern[index]) == ']') { + RETokenChar t = new RETokenChar(subIndex,lastChar,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + lastChar = '-'; + } else { + if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) { + CharExpression ce = getCharExpression(pattern, index, pLength, syntax); + if (ce == null) + throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); + ch = ce.ch; + index = index + ce.len - 1; + } + RETokenRange t = new RETokenRange(subIndex,lastChar,ch,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + lastChar = 0; lastCharIsSet = false; + index++; + } + } else if ((ch == '\\') && syntax.get(RESyntax.RE_BACKSLASH_ESCAPE_IN_LISTS)) { + if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + int posixID = -1; + boolean negate = false; + char asciiEsc = 0; + boolean asciiEscIsSet = false; + NamedProperty np = null; + if (("dswDSW".indexOf(pattern[index]) != -1) && syntax.get(RESyntax.RE_CHAR_CLASS_ESC_IN_LISTS)) { + switch (pattern[index]) { + case 'D': + negate = true; + case 'd': + posixID = RETokenPOSIX.DIGIT; + break; + case 'S': + negate = true; + case 's': + posixID = RETokenPOSIX.SPACE; + break; + case 'W': + negate = true; + case 'w': + posixID = RETokenPOSIX.ALNUM; + break; + } + } + if (("pP".indexOf(pattern[index]) != -1) && syntax.get(RESyntax.RE_NAMED_PROPERTY)) { + np = getNamedProperty(pattern, index - 1, pLength); + if (np == null) + throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); + index = index - 1 + np.len - 1; + } + else { + CharExpression ce = getCharExpression(pattern, index - 1, pLength, syntax); + if (ce == null) + throw new REException("invalid escape sequence", REException.REG_ESCAPE, index); + asciiEsc = ce.ch; asciiEscIsSet = true; + index = index - 1 + ce.len - 1; + } + if (lastCharIsSet) { + RETokenChar t = new RETokenChar(subIndex,lastChar,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } + + if (posixID != -1) { + RETokenPOSIX t = new RETokenPOSIX(subIndex,posixID,insens,negate); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } else if (np != null) { + RETokenNamedProperty t = getRETokenNamedProperty(subIndex,np,insens,index); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } else if (asciiEscIsSet) { + lastChar = asciiEsc; lastCharIsSet = true; + } else { + lastChar = pattern[index]; lastCharIsSet = true; + } + ++index; + } else if ((ch == '[') && (syntax.get(RESyntax.RE_CHAR_CLASSES)) && (index < pLength) && (pattern[index] == ':')) { + StringBuffer posixSet = new StringBuffer(); + index = getPosixSet(pattern,index+1,posixSet); + int posixId = RETokenPOSIX.intValue(posixSet.toString()); + if (posixId != -1) { + RETokenPOSIX t = new RETokenPOSIX(subIndex,posixId,insens,false); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } + } else if ((ch == '[') && (syntax.get(RESyntax.RE_NESTED_CHARCLASS))) { + ParseCharClassResult result = parseCharClass( + subIndex, pattern, index, pLength, cflags, syntax, 0); + addition.addElement(result.token); + addition.addElement("|"); + index = result.index; + } else if ((ch == '&') && + (syntax.get(RESyntax.RE_NESTED_CHARCLASS)) && + (index < pLength) && (pattern[index] == '&')) { + if (returnAtAndOperator) { + ParseCharClassResult result = new ParseCharClassResult(); + options.trimToSize(); + if (additionAndAppeared) addition.addElement("&"); + if (addition.size() == 0) addition = null; + result.token = new RETokenOneOf(subIndex, + options, addition, negative); + result.index = index - 1; + result.returnAtAndOperator = true; + return result; + } + // The precedence of the operator "&&" is the lowest. + // So we postpone adding "&" until other elements + // are added. And we insert Boolean.FALSE at the + // beginning of the list of tokens following "&&". + // So, "&&[a-b][k-m]" will be stored in the Vecter + // addition in this order: + // Boolean.FALSE, [a-b], "|", [k-m], "|", "&" + if (additionAndAppeared) addition.addElement("&"); + addition.addElement(Boolean.FALSE); + additionAndAppeared = true; + + // The part on which "&&" operates may be either + // (1) explicitly enclosed by [] + // or + // (2) not enclosed by [] and terminated by the + // next "&&" or the end of the character list. + // Let the preceding else if block do the case (1). + // We must do something in case of (2). + if ((index + 1 < pLength) && (pattern[index + 1] != '[')) { + ParseCharClassResult result = parseCharClass( + subIndex, pattern, index+1, pLength, cflags, syntax, + RETURN_AT_AND); + addition.addElement(result.token); + addition.addElement("|"); + // If the method returned at the next "&&", it is OK. + // Otherwise we have eaten the mark of the end of this + // character list "]". In this case we must give back + // the end mark. + index = (result.returnAtAndOperator ? + result.index: result.index - 1); + } + } else { + if (lastCharIsSet) { + RETokenChar t = new RETokenChar(subIndex,lastChar,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } + lastChar = ch; lastCharIsSet = true; + } + if (index == pLength) throw new REException(getLocalizedMessage("class.no.end"),REException.REG_EBRACK,index); + } // while in list + // Out of list, index is one past ']' + + if (lastCharIsSet) { + RETokenChar t = new RETokenChar(subIndex,lastChar,insens); + if (insensUSASCII) t.unicodeAware = false; + options.addElement(t); + } + + ParseCharClassResult result = new ParseCharClassResult(); + // Create a new RETokenOneOf + options.trimToSize(); + if (additionAndAppeared) addition.addElement("&"); + if (addition.size() == 0) addition = null; + result.token = new RETokenOneOf(subIndex,options, addition, negative); + result.index = index; + return result; + } + + private static int getCharUnit(char[] input, int index, CharUnit unit, boolean quot) throws REException { + unit.ch = input[index++]; + unit.bk = (unit.ch == '\\' + && (!quot || index >= input.length || input[index] == 'E')); + if (unit.bk) + if (index < input.length) + unit.ch = input[index++]; + else throw new REException(getLocalizedMessage("ends.with.backslash"),REException.REG_ESCAPE,index); + return index; + } + + private static int parseInt(char[] input, int pos, int len, int radix) { + int ret = 0; + for (int i = pos; i < pos + len; i++) { + ret = ret * radix + Character.digit(input[i], radix); + } + return ret; + } + + /** + * This class represents various expressions for a character. + * "a" : 'a' itself. + * "\0123" : Octal char 0123 + * "\x1b" : Hex char 0x1b + * "\u1234" : Unicode char \u1234 + */ + private static class CharExpression { + /** character represented by this expression */ + char ch; + /** String expression */ + String expr; + /** length of this expression */ + int len; + public String toString() { return expr; } + } + + private static CharExpression getCharExpression(char[] input, int pos, int lim, + RESyntax syntax) { + CharExpression ce = new CharExpression(); + char c = input[pos]; + if (c == '\\') { + if (pos + 1 >= lim) return null; + c = input[pos + 1]; + switch(c) { + case 't': + ce.ch = '\t'; + ce.len = 2; + break; + case 'n': + ce.ch = '\n'; + ce.len = 2; + break; + case 'r': + ce.ch = '\r'; + ce.len = 2; + break; + case 'x': + case 'u': + if ((c == 'x' && syntax.get(RESyntax.RE_HEX_CHAR)) || + (c == 'u' && syntax.get(RESyntax.RE_UNICODE_CHAR))) { + int l = 0; + int expectedLength = (c == 'x' ? 2 : 4); + for (int i = pos + 2; i < pos + 2 + expectedLength; i++) { + if (i >= lim) break; + if (!((input[i] >= '0' && input[i] <= '9') || + (input[i] >= 'A' && input[i] <= 'F') || + (input[i] >= 'a' && input[i] <= 'f'))) + break; + l++; + } + if (l != expectedLength) return null; + ce.ch = (char)(parseInt(input, pos + 2, l, 16)); + ce.len = l + 2; + } + else { + ce.ch = c; + ce.len = 2; + } + break; + case '0': + if (syntax.get(RESyntax.RE_OCTAL_CHAR)) { + int l = 0; + for (int i = pos + 2; i < pos + 2 + 3; i++) { + if (i >= lim) break; + if (input[i] < '0' || input[i] > '7') break; + l++; + } + if (l == 3 && input[pos + 2] > '3') l--; + if (l <= 0) return null; + ce.ch = (char)(parseInt(input, pos + 2, l, 8)); + ce.len = l + 2; + } + else { + ce.ch = c; + ce.len = 2; + } + break; + default: + ce.ch = c; + ce.len = 2; + break; + } + } + else { + ce.ch = input[pos]; + ce.len = 1; + } + ce.expr = new String(input, pos, ce.len); + return ce; + } + + /** + * This class represents a substring in a pattern string expressing + * a named property. + * "\pA" : Property named "A" + * "\p{prop}" : Property named "prop" + * "\PA" : Property named "A" (Negated) + * "\P{prop}" : Property named "prop" (Negated) + */ + private static class NamedProperty { + /** Property name */ + String name; + /** Negated or not */ + boolean negate; + /** length of this expression */ + int len; + } + + private static NamedProperty getNamedProperty(char[] input, int pos, int lim) { + NamedProperty np = new NamedProperty(); + char c = input[pos]; + if (c == '\\') { + if (++pos >= lim) return null; + c = input[pos++]; + switch(c) { + case 'p': + np.negate = false; + break; + case 'P': + np.negate = true; + break; + default: + return null; + } + c = input[pos++]; + if (c == '{') { + int p = -1; + for (int i = pos; i < lim; i++) { + if (input[i] == '}') { + p = i; + break; + } + } + if (p < 0) return null; + int len = p - pos; + np.name = new String(input, pos, len); + np.len = len + 4; + } + else { + np.name = new String(input, pos - 1, 1); + np.len = 3; + } + return np; + } + else return null; + } + + private static RETokenNamedProperty getRETokenNamedProperty( + int subIndex, NamedProperty np, boolean insens, int index) + throws REException { + try { + return new RETokenNamedProperty(subIndex, np.name, insens, np.negate); + } + catch (REException e) { + REException ree; + ree = new REException(e.getMessage(), REException.REG_ESCAPE, index); + ree.initCause(e); + throw ree; + } + } + + /** + * Checks if the regular expression matches the input in its entirety. + * + * @param input The input text. + */ + public boolean isMatch(Object input) { + return isMatch(input,0,0); + } + + /** + * Checks if the input string, starting from index, is an exact match of + * this regular expression. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + */ + public boolean isMatch(Object input,int index) { + return isMatch(input,index,0); + } + + + /** + * Checks if the input, starting from index and using the specified + * execution flags, is an exact match of this regular expression. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + */ + public boolean isMatch(Object input,int index,int eflags) { + return isMatchImpl(makeCharIndexed(input,index),index,eflags); + } + + private boolean isMatchImpl(CharIndexed input, int index, int eflags) { + if (firstToken == null) // Trivial case + return (input.charAt(0) == CharIndexed.OUT_OF_BOUNDS); + REMatch m = new REMatch(numSubs, index, eflags); + if (firstToken.match(input, m)) { + if (m != null) { + if (input.charAt(m.index) == CharIndexed.OUT_OF_BOUNDS) { + return true; + } + } + } + return false; + } + + /** + * Returns the maximum number of subexpressions in this regular expression. + * If the expression contains branches, the value returned will be the + * maximum subexpressions in any of the branches. + */ + public int getNumSubs() { + return numSubs; + } + + // Overrides REToken.setUncle + void setUncle(REToken uncle) { + if (lastToken != null) { + lastToken.setUncle(uncle); + } else super.setUncle(uncle); // to deal with empty subexpressions + } + + // Overrides REToken.chain + + boolean chain(REToken next) { + super.chain(next); + setUncle(next); + return true; + } + + /** + * Returns the minimum number of characters that could possibly + * constitute a match of this regular expression. + */ + public int getMinimumLength() { + return minimumLength; + } + + public int getMaximumLength() { + return maximumLength; + } + + /** + * Returns an array of all matches found in the input. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches(Object input) { + return getAllMatches(input,0,0); + } + + /** + * Returns an array of all matches found in the input, + * beginning at the specified index position. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches(Object input, int index) { + return getAllMatches(input,index,0); + } + + /** + * Returns an array of all matches found in the input string, + * beginning at the specified index position and using the specified + * execution flags. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return a non-null (but possibly zero-length) array of matches + */ + public REMatch[] getAllMatches(Object input, int index, int eflags) { + return getAllMatchesImpl(makeCharIndexed(input,index),index,eflags); + } + + // this has been changed since 1.03 to be non-overlapping matches + private REMatch[] getAllMatchesImpl(CharIndexed input, int index, int eflags) { + Vector all = new Vector(); + REMatch m = null; + while ((m = getMatchImpl(input,index,eflags,null)) != null) { + all.addElement(m); + index = m.getEndIndex(); + if (m.end[0] == 0) { // handle pathological case of zero-length match + index++; + input.move(1); + } else { + input.move(m.end[0]); + } + if (!input.isValid()) break; + } + REMatch[] mset = new REMatch[all.size()]; + all.copyInto(mset); + return mset; + } + + /* Implements abstract method REToken.match() */ + boolean match(CharIndexed input, REMatch mymatch) { + if (firstToken == null) { + return next(input, mymatch); + } + + // Note the start of this subexpression + mymatch.start1[subIndex] = mymatch.index; + + return firstToken.match(input, mymatch); + } + + REMatch findMatch(CharIndexed input, REMatch mymatch) { + if (mymatch.backtrackStack == null) + mymatch.backtrackStack = new BacktrackStack(); + boolean b = match(input, mymatch); + if (b) { + return mymatch; + } + return null; + } + + /** + * Returns the first match found in the input. If no match is found, + * null is returned. + * + * @param input The input text. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch(Object input) { + return getMatch(input,0,0); + } + + /** + * Returns the first match found in the input, beginning + * the search at the specified index. If no match is found, + * returns null. + * + * @param input The input text. + * @param index The offset within the text to begin looking for a match. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch(Object input, int index) { + return getMatch(input,index,0); + } + + /** + * Returns the first match found in the input, beginning + * the search at the specified index, and using the specified + * execution flags. If no match is found, returns null. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return An REMatch instance referencing the match, or null if none. + */ + public REMatch getMatch(Object input, int index, int eflags) { + return getMatch(input,index,eflags,null); + } + + /** + * Returns the first match found in the input, beginning the search + * at the specified index, and using the specified execution flags. + * If no match is found, returns null. If a StringBuffer is + * provided and is non-null, the contents of the input text from the + * index to the beginning of the match (or to the end of the input, + * if there is no match) are appended to the StringBuffer. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @param buffer The StringBuffer to save pre-match text in. + * @return An REMatch instance referencing the match, or null if none. */ + public REMatch getMatch(Object input, int index, int eflags, StringBuffer buffer) { + return getMatchImpl(makeCharIndexed(input,index),index,eflags,buffer); + } + + REMatch getMatchImpl(CharIndexed input, int anchor, int eflags, StringBuffer buffer) { + boolean tryEntireMatch = ((eflags & REG_TRY_ENTIRE_MATCH) != 0); + RE re = (tryEntireMatch ? (RE) this.clone() : this); + if (tryEntireMatch) { + re.chain(new RETokenEnd(0, null)); + } + // Create a new REMatch to hold results + REMatch mymatch = new REMatch(numSubs, anchor, eflags); + do { + // Optimization: check if anchor + minimumLength > length + if (minimumLength == 0 || input.charAt(minimumLength-1) != CharIndexed.OUT_OF_BOUNDS) { + if (re.match(input, mymatch)) { + REMatch best = mymatch; + // We assume that the match that coms first is the best. + // And the following "The longer, the better" rule has + // been commented out. The longest is not neccesarily + // the best. For example, "a" out of "aaa" is the best + // match for /a+?/. + /* + // Find best match of them all to observe leftmost longest + while ((mymatch = mymatch.next) != null) { + if (mymatch.index > best.index) { + best = mymatch; + } + } + */ + best.end[0] = best.index; + best.finish(input); + input.setLastMatch(best); + return best; + } + } + mymatch.clear(++anchor); + // Append character to buffer if needed + if (buffer != null && input.charAt(0) != CharIndexed.OUT_OF_BOUNDS) { + buffer.append(input.charAt(0)); + } + } while (input.move(1)); + + // Special handling at end of input for e.g. "$" + if (minimumLength == 0) { + if (match(input, mymatch)) { + mymatch.finish(input); + return mymatch; + } + } + + return null; + } + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @return A non-null REMatchEnumeration instance. + */ + public REMatchEnumeration getMatchEnumeration(Object input) { + return getMatchEnumeration(input,0,0); + } + + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @return A non-null REMatchEnumeration instance, with its input cursor + * set to the index position specified. + */ + public REMatchEnumeration getMatchEnumeration(Object input, int index) { + return getMatchEnumeration(input,index,0); + } + + /** + * Returns an REMatchEnumeration that can be used to iterate over the + * matches found in the input text. + * + * @param input The input text. + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A non-null REMatchEnumeration instance, with its input cursor + * set to the index position specified. + */ + public REMatchEnumeration getMatchEnumeration(Object input, int index, int eflags) { + return new REMatchEnumeration(this,makeCharIndexed(input,index),index,eflags); + } + + + /** + * Substitutes the replacement text for the first match found in the input. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @return A String interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute(Object input,String replace) { + return substitute(input,replace,0,0); + } + + /** + * Substitutes the replacement text for the first match found in the input + * beginning at the specified index position. Specifying an index + * effectively causes the regular expression engine to throw away the + * specified number of characters. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute(Object input,String replace,int index) { + return substitute(input,replace,index,0); + } + + /** + * Substitutes the replacement text for the first match found in the input + * string, beginning at the specified index position and using the + * specified execution flags. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substitute(Object input,String replace,int index,int eflags) { + return substituteImpl(makeCharIndexed(input,index),replace,index,eflags); + } + + private String substituteImpl(CharIndexed input,String replace,int index,int eflags) { + StringBuffer buffer = new StringBuffer(); + REMatch m = getMatchImpl(input,index,eflags,buffer); + if (m==null) return buffer.toString(); + buffer.append(getReplacement(replace, m, eflags)); + if (input.move(m.end[0])) { + do { + buffer.append(input.charAt(0)); + } while (input.move(1)); + } + return buffer.toString(); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @return A String interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll(Object input,String replace) { + return substituteAll(input,replace,0,0); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text, starting at the specified index. + * + * If the regular expression allows the empty string to match, it will + * substitute matches at all positions except the end of the input. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll(Object input,String replace,int index) { + return substituteAll(input,replace,index,0); + } + + /** + * Substitutes the replacement text for each non-overlapping match found + * in the input text, starting at the specified index and using the + * specified execution flags. + * + * @param input The input text. + * @param replace The replacement text, which may contain $x metacharacters (see REMatch.substituteInto). + * @param index The offset index at which the search should be begin. + * @param eflags The logical OR of any execution flags above. + * @return A String containing the substring of the input, starting + * at the index position, and interpolating the substituted text. + * @see REMatch#substituteInto + */ + public String substituteAll(Object input,String replace,int index,int eflags) { + return substituteAllImpl(makeCharIndexed(input,index),replace,index,eflags); + } + + private String substituteAllImpl(CharIndexed input,String replace,int index,int eflags) { + StringBuffer buffer = new StringBuffer(); + REMatch m; + while ((m = getMatchImpl(input,index,eflags,buffer)) != null) { + buffer.append(getReplacement(replace, m, eflags)); + index = m.getEndIndex(); + if (m.end[0] == 0) { + char ch = input.charAt(0); + if (ch != CharIndexed.OUT_OF_BOUNDS) + buffer.append(ch); + input.move(1); + } else { + input.move(m.end[0]); + } + + if (!input.isValid()) break; + } + return buffer.toString(); + } + + public static String getReplacement(String replace, REMatch m, int eflags) { + if ((eflags & REG_NO_INTERPOLATE) > 0) + return replace; + else { + if ((eflags & REG_REPLACE_USE_BACKSLASHESCAPE) > 0) { + StringBuffer sb = new StringBuffer(); + int l = replace.length(); + for (int i = 0; i < l; i++) { + char c = replace.charAt(i); + switch(c) { + case '\\': + i++; + // Let StringIndexOutOfBoundsException be thrown. + sb.append(replace.charAt(i)); + break; + case '$': + int i1 = i + 1; + while (i1 < replace.length() && + Character.isDigit(replace.charAt(i1))) i1++; + sb.append(m.substituteInto(replace.substring(i, i1))); + i = i1 - 1; + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + else + return m.substituteInto(replace); + } + } + + /* Helper function for constructor */ + private void addToken(REToken next) { + if (next == null) return; + minimumLength += next.getMinimumLength(); + int nmax = next.getMaximumLength(); + if (nmax < Integer.MAX_VALUE && maximumLength < Integer.MAX_VALUE) + maximumLength += nmax; + else + maximumLength = Integer.MAX_VALUE; + + if (firstToken == null) { + lastToken = firstToken = next; + } else { + // if chain returns false, it "rejected" the token due to + // an optimization, and next was combined with lastToken + if (lastToken.chain(next)) { + lastToken = next; + } + } + } + + private static REToken setRepeated(REToken current, int min, int max, int index) throws REException { + if (current == null) throw new REException(getLocalizedMessage("repeat.no.token"),REException.REG_BADRPT,index); + return new RETokenRepeated(current.subIndex,current,min,max); + } + + private static int getPosixSet(char[] pattern,int index,StringBuffer buf) { + // Precondition: pattern[index-1] == ':' + // we will return pos of closing ']'. + int i; + for (i=index; i<(pattern.length-1); i++) { + if ((pattern[i] == ':') && (pattern[i+1] == ']')) + return i+2; + buf.append(pattern[i]); + } + return index; // didn't match up + } + + private int getMinMax(char[] input,int index,IntPair minMax,RESyntax syntax) throws REException { + // Precondition: input[index-1] == '{', minMax != null + + boolean mustMatch = !syntax.get(RESyntax.RE_NO_BK_BRACES); + int startIndex = index; + if (index == input.length) { + if (mustMatch) + throw new REException(getLocalizedMessage("unmatched.brace"),REException.REG_EBRACE,index); + else + return startIndex; + } + + int min,max=0; + CharUnit unit = new CharUnit(); + StringBuffer buf = new StringBuffer(); + + // Read string of digits + do { + index = getCharUnit(input,index,unit,false); + if (Character.isDigit(unit.ch)) + buf.append(unit.ch); + } while ((index != input.length) && Character.isDigit(unit.ch)); + + // Check for {} tomfoolery + if (buf.length() == 0) { + if (mustMatch) + throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); + else + return startIndex; + } + + min = Integer.parseInt(buf.toString()); + + if ((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk)) + max = min; + else if (index == input.length) + if (mustMatch) + throw new REException(getLocalizedMessage("interval.no.end"),REException.REG_EBRACE,index); + else + return startIndex; + else if ((unit.ch == ',') && !unit.bk) { + buf = new StringBuffer(); + // Read string of digits + while (((index = getCharUnit(input,index,unit,false)) != input.length) && Character.isDigit(unit.ch)) + buf.append(unit.ch); + + if (!((unit.ch == '}') && (syntax.get(RESyntax.RE_NO_BK_BRACES) ^ unit.bk))) + if (mustMatch) + throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); + else + return startIndex; + + // This is the case of {x,} + if (buf.length() == 0) max = Integer.MAX_VALUE; + else max = Integer.parseInt(buf.toString()); + } else + if (mustMatch) + throw new REException(getLocalizedMessage("interval.error"),REException.REG_EBRACE,index); + else + return startIndex; + + // We know min and max now, and they are valid. + + minMax.first = min; + minMax.second = max; + + // return the index following the '}' + return index; + } + + /** + * Return a human readable form of the compiled regular expression, + * useful for debugging. + */ + public String toString() { + StringBuffer sb = new StringBuffer(); + dump(sb); + return sb.toString(); + } + + void dump(StringBuffer os) { + os.append("(?#startRE subIndex=" + subIndex + ")"); + if (subIndex == 0) + os.append("?:"); + if (firstToken != null) + firstToken.dumpAll(os); + if (subIndex == 0) + os.append(")"); + os.append("(?#endRE subIndex=" + subIndex + ")"); + } + + // Cast input appropriately or throw exception + // This method was originally a private method, but has been made + // public because java.util.regex.Matcher uses this. + public static CharIndexed makeCharIndexed(Object input, int index) { + // The case where input is already a CharIndexed is supposed + // be the most likely because this is the case with + // java.util.regex.Matcher. + // We could let a String or a CharSequence fall through + // to final input, but since it'a very likely input type, + // we check it first. + if (input instanceof CharIndexed) { + CharIndexed ci = (CharIndexed) input; + ci.setAnchor(index); + return ci; + } + else if (input instanceof CharSequence) + return new CharIndexedCharSequence((CharSequence) input,index); + else if (input instanceof String) + return new CharIndexedString((String) input,index); + else if (input instanceof char[]) + return new CharIndexedCharArray((char[]) input,index); + else if (input instanceof StringBuffer) + return new CharIndexedStringBuffer((StringBuffer) input,index); + else if (input instanceof InputStream) + return new CharIndexedInputStream((InputStream) input,index); + else + return new CharIndexedString(input.toString(), index); + } +} diff --git a/gnu/java/util/regex/REException.java b/gnu/java/util/regex/REException.java new file mode 100644 index 000000000..4104fbcd8 --- /dev/null +++ b/gnu/java/util/regex/REException.java @@ -0,0 +1,182 @@ +/* gnu/regexp/REException.java + Copyright (C) 1998-2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +import java.text.MessageFormat; + +/** + * This is the regular expression exception class. An exception of this type + * defines the three attributes: + * <OL> + * <LI> A descriptive message of the error. + * <LI> An integral type code equivalent to one of the statically + * defined symbols listed below. + * <LI> The approximate position in the input string where the error + * occurred. + * </OL> + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ + +public class REException extends Exception { + private int type; + private int pos; + + // Error conditions from GNU regcomp(3) manual + + /** + * Error flag. + * Invalid use of repetition operators such as using + * `*' as the first character. + */ + public static final int REG_BADRPT = 1; + + /** + * Error flag. + * Invalid use of back reference operator. + */ + public static final int REG_BADBR = 2; + + /** + * Error flag. + * Un-matched brace interval operators. + */ + public static final int REG_EBRACE = 3; + + /** + * Error flag. + * Un-matched bracket list operators. + */ + public static final int REG_EBRACK = 4; + + /** + * Error flag. + * Invalid use of the range operator, eg. the ending + * point of the range occurs prior to the starting + * point. + */ + public static final int REG_ERANGE = 5; + + /** + * Error flag. + * Unknown character class name. <B>Not implemented</B>. + */ + public static final int REG_ECTYPE = 6; + + /** + * Error flag. + * Un-matched parenthesis group operators. + */ + public static final int REG_EPAREN = 7; + + /** + * Error flag. + * Invalid back reference to a subexpression. + */ + public static final int REG_ESUBREG = 8; + + /** + * Error flag. + * Non specific error. <B>Not implemented</B>. + */ + public static final int REG_EEND = 9; + + /** + * Error flag. + * Invalid escape sequence. <B>Not implemented</B>. + */ + public static final int REG_ESCAPE = 10; + + /** + * Error flag. + * Invalid use of pattern operators such as group or list. + */ + public static final int REG_BADPAT = 11; + + /** + * Error flag. + * Compiled regular expression requires a pattern + * buffer larger than 64Kb. <B>Not implemented</B>. + */ + public static final int REG_ESIZE = 12; + + /** + * Error flag. + * The regex routines ran out of memory. <B>Not implemented</B>. + */ + public static final int REG_ESPACE = 13; + + REException(String msg, int type, int position) { + super(msg); + this.type = type; + this.pos = position; + } + + /** + * Returns the type of the exception, one of the constants listed above. + */ + + public int getType() { + return type; + } + + /** + * Returns the position, relative to the string or character array being + * compiled, where the error occurred. This position is generally the point + * where the error was detected, not necessarily the starting index of + * a bad subexpression. + */ + public int getPosition() { + return pos; + } + + /** + * Reports the descriptive message associated with this exception + * as well as its index position in the string or character array + * being compiled. + */ + public String getMessage() { + Object[] args = {new Integer(pos)}; + StringBuffer sb = new StringBuffer(); + String prefix = RE.getLocalizedMessage("error.prefix"); + sb.append(MessageFormat.format(prefix, args)); + sb.append('\n'); + sb.append(super.getMessage()); + return sb.toString(); + } +} diff --git a/gnu/java/util/regex/REFilterInputStream.java b/gnu/java/util/regex/REFilterInputStream.java new file mode 100644 index 000000000..abe86308b --- /dev/null +++ b/gnu/java/util/regex/REFilterInputStream.java @@ -0,0 +1,140 @@ +/* gnu/regexp/REFilterInputStream.java + Copyright (C) 1998-2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package gnu.java.util.regex; +import java.io.FilterInputStream; +import java.io.InputStream; + +/** + * Replaces instances of a given RE found within an InputStream + * with replacement text. The replacements are interpolated into the + * stream when a match is found. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + * @deprecated This class cannot properly handle all character + * encodings. For proper handling, use the REFilterReader + * class instead. + */ + +public class REFilterInputStream extends FilterInputStream { + + private RE expr; + private String replace; + private String buffer; + private int bufpos; + private int offset; + private CharIndexedInputStream stream; + + /** + * Creates an REFilterInputStream. When reading from this stream, + * occurrences of patterns matching the supplied regular expression + * will be replaced with the supplied replacement text (the + * metacharacters $0 through $9 may be used to refer to the full + * match or subexpression matches). + * + * @param stream The InputStream to be filtered. + * @param expr The regular expression to search for. + * @param replace The text pattern to replace matches with. + */ + public REFilterInputStream(InputStream stream, RE expr, String replace) { + super(stream); + this.stream = new CharIndexedInputStream(stream,0); + this.expr = expr; + this.replace = replace; + } + + /** + * Reads the next byte from the stream per the general contract of + * InputStream.read(). Returns -1 on error or end of stream. + */ + public int read() { + // If we have buffered replace data, use it. + if ((buffer != null) && (bufpos < buffer.length())) { + return (int) buffer.charAt(bufpos++); + } + + // check if input is at a valid position + if (!stream.isValid()) return -1; + + REMatch mymatch = new REMatch(expr.getNumSubs(),offset,0); + if (expr.match(stream, mymatch)) { + mymatch.end[0] = mymatch.index; + mymatch.finish(stream); + stream.move(mymatch.toString().length()); + offset += mymatch.toString().length(); + buffer = mymatch.substituteInto(replace); + bufpos = 1; + + // This is prone to infinite loops if replace string turns out empty. + if (buffer.length() > 0) { + return buffer.charAt(0); + } + } + char ch = stream.charAt(0); + if (ch == CharIndexed.OUT_OF_BOUNDS) return -1; + stream.move(1); + offset++; + return ch; + } + + /** + * Returns false. REFilterInputStream does not support mark() and + * reset() methods. + */ + public boolean markSupported() { + return false; + } + + /** Reads from the stream into the provided array. */ + public int read(byte[] b, int off, int len) { + int i; + int ok = 0; + while (len-- > 0) { + i = read(); + if (i == -1) return (ok == 0) ? -1 : ok; + b[off++] = (byte) i; + ok++; + } + return ok; + } + + /** Reads from the stream into the provided array. */ + public int read(byte[] b) { + return read(b,0,b.length); + } +} diff --git a/gnu/java/util/regex/REMatch.java b/gnu/java/util/regex/REMatch.java new file mode 100644 index 000000000..3ff5ad794 --- /dev/null +++ b/gnu/java/util/regex/REMatch.java @@ -0,0 +1,324 @@ +/* gnu/regexp/REMatch.java + 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.util.regex; +import java.io.Serializable; + +/** + * An instance of this class represents a match + * completed by a gnu.regexp matching function. It can be used + * to obtain relevant information about the location of a match + * or submatch. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ +public final class REMatch implements Serializable, Cloneable { + private String matchedText; + private CharIndexed matchedCharIndexed; + + // These variables are package scope for fast access within the engine + int eflags; // execution flags this match was made using + + // Offset in source text where match was tried. This is zero-based; + // the actual position in the source text is given by (offset + anchor). + int offset; + + // Anchor position refers to the index into the source input + // at which the matching operation began. + // This is also useful for the ANCHORINDEX option. + int anchor; + + // Package scope; used by RE. + int index; // used while matching to mark current match position in input + // start1[i] is set when the i-th subexp starts. And start1[i] is copied + // to start[i] when the i-th subexp ends. So start[i] keeps the previously + // assigned value while the i-th subexp is being processed. This makes + // backreference to the i-th subexp within the i-th subexp possible. + int[] start; // start positions (relative to offset) for each (sub)exp. + int[] start1; // start positions (relative to offset) for each (sub)exp. + int[] end; // end positions for the same + // start[i] == -1 or end[i] == -1 means that the start/end position is void. + // start[i] == p or end[i] == p where p < 0 and p != -1 means that + // the actual start/end position is (p+1). Start/end positions may + // become negative when the subexpression is in a RETokenLookBehind. + boolean empty; // empty string matched. This flag is used only within + // RETokenRepeated. + + BacktrackStack backtrackStack; + + public Object clone() { + try { + REMatch copy = (REMatch) super.clone(); + + copy.start = (int[]) start.clone(); + copy.start1 = (int[]) start1.clone(); + copy.end = (int[]) end.clone(); + + return copy; + } catch (CloneNotSupportedException e) { + throw new Error(); // doesn't happen + } + } + + void assignFrom(REMatch other) { + start = other.start; + start1 = other.start1; + end = other.end; + index = other.index; + backtrackStack = other.backtrackStack; + } + + REMatch(int subs, int anchor, int eflags) { + start = new int[subs+1]; + start1 = new int[subs+1]; + end = new int[subs+1]; + this.anchor = anchor; + this.eflags = eflags; + clear(anchor); + } + + void finish(CharIndexed text) { + start[0] = 0; + StringBuffer sb = new StringBuffer(); + int i; + for (i = 0; i < end[0]; i++) + sb.append(text.charAt(i)); + matchedText = sb.toString(); + matchedCharIndexed = text; + for (i = 0; i < start.length; i++) { + // If any subexpressions didn't terminate, they don't count + // TODO check if this code ever gets hit + if ((start[i] == -1) ^ (end[i] == -1)) { + start[i] = -1; + end[i] = -1; + } + } + backtrackStack = null; + } + + /** Clears the current match and moves the offset to the new index. */ + void clear(int index) { + offset = index; + this.index = 0; + for (int i = 0; i < start.length; i++) { + start[i] = start1[i] = end[i] = -1; + } + backtrackStack = null; + } + + /** + * Returns the string matching the pattern. This makes it convenient + * to write code like the following: + * <P> + * <code> + * REMatch myMatch = myExpression.getMatch(myString);<br> + * if (myMatch != null) System.out.println("Regexp found: "+myMatch); + * </code> + */ + public String toString() { + return matchedText; + } + + /** + * Returns the index within the input text where the match in its entirety + * began. + */ + public int getStartIndex() { + return offset + start[0]; + } + + /** + * Returns the index within the input string where the match in + * its entirety ends. The return value is the next position after + * the end of the string; therefore, a match created by the + * following call: + * + * <P> + * <code>REMatch myMatch = myExpression.getMatch(myString);</code> + * <P> + * can be viewed (given that myMatch is not null) by creating + * <P> + * <code>String theMatch = myString.substring(myMatch.getStartIndex(), + * myMatch.getEndIndex());</code> + * <P> + * But you can save yourself that work, since the <code>toString()</code> + * method (above) does exactly that for you. + */ + public int getEndIndex() { + return offset + end[0]; + } + + /** + * Returns the string matching the given subexpression. The subexpressions + * are indexed starting with one, not zero. That is, the subexpression + * identified by the first set of parentheses in a regular expression + * could be retrieved from an REMatch by calling match.toString(1). + * + * @param sub Index of the subexpression. + */ + public String toString(int sub) { + if ((sub >= start.length) || sub < 0) + throw new IndexOutOfBoundsException("No group " + sub); + if (start[sub] == -1) return null; + if (start[sub] >= 0 && end[sub] <= matchedText.length()) + return (matchedText.substring(start[sub],end[sub])); + else { + // This case occurs with RETokenLookAhead or RETokenLookBehind. + StringBuffer sb = new StringBuffer(); + int s = start[sub]; + int e = end[sub]; + if (s < 0) s += 1; + if (e < 0) e += 1; + for (int i = start[0] + s; i < start[0] + e; i++) + sb.append(matchedCharIndexed.charAt(i)); + return sb.toString(); + } + } + + /** + * Returns the index within the input string used to generate this match + * where subexpression number <i>sub</i> begins, or <code>-1</code> if + * the subexpression does not exist. The initial position is zero. + * + * @param sub Subexpression index + * @deprecated Use getStartIndex(int) instead. + */ + public int getSubStartIndex(int sub) { + if (sub >= start.length) return -1; + int x = start[sub]; + return (x == -1) ? x : + (x >= 0) ? offset + x : offset + x + 1; + } + + /** + * Returns the index within the input string used to generate this match + * where subexpression number <i>sub</i> begins, or <code>-1</code> if + * the subexpression does not exist. The initial position is zero. + * + * @param sub Subexpression index + * @since gnu.regexp 1.1.0 + */ + public int getStartIndex(int sub) { + if (sub >= start.length) return -1; + int x = start[sub]; + return (x == -1) ? x : + (x >= 0) ? offset + x : offset + x + 1; + } + + /** + * Returns the index within the input string used to generate this match + * where subexpression number <i>sub</i> ends, or <code>-1</code> if + * the subexpression does not exist. The initial position is zero. + * + * @param sub Subexpression index + * @deprecated Use getEndIndex(int) instead + */ + public int getSubEndIndex(int sub) { + if (sub >= start.length) return -1; + int x = end[sub]; + return (x == -1) ? x : + (x >= 0) ? offset + x : offset + x + 1; + } + + /** + * Returns the index within the input string used to generate this match + * where subexpression number <i>sub</i> ends, or <code>-1</code> if + * the subexpression does not exist. The initial position is zero. + * + * @param sub Subexpression index + */ + public int getEndIndex(int sub) { + if (sub >= start.length) return -1; + int x = end[sub]; + return (x == -1) ? x : + (x >= 0) ? offset + x : offset + x + 1; + } + + /** + * Substitute the results of this match to create a new string. + * This is patterned after PERL, so the tokens to watch out for are + * <code>$0</code> through <code>$9</code>. <code>$0</code> matches + * the full substring matched; <code>$<i>n</i></code> matches + * subexpression number <i>n</i>. + * <code>$10, $11, ...</code> may match the 10th, 11th, ... subexpressions + * if such subexpressions exist. + * + * @param input A string consisting of literals and <code>$<i>n</i></code> tokens. + */ + public String substituteInto(String input) { + // a la Perl, $0 is whole thing, $1 - $9 are subexpressions + StringBuffer output = new StringBuffer(); + int pos; + for (pos = 0; pos < input.length()-1; pos++) { + if ((input.charAt(pos) == '$') && (Character.isDigit(input.charAt(pos+1)))) { + int val = Character.digit(input.charAt(++pos),10); + int pos1 = pos + 1; + while (pos1 < input.length() && + Character.isDigit(input.charAt(pos1))) { + int val1 = val*10 + Character.digit(input.charAt(pos1),10); + if (val1 >= start.length) break; + pos1++; + val = val1; + } + pos = pos1 - 1; + + if (val < start.length) { + output.append(toString(val)); + } + } else output.append(input.charAt(pos)); + } + if (pos < input.length()) output.append(input.charAt(pos)); + return output.toString(); + } + +/* The following are used for debugging purpose + static String d(REMatch m) { + if (m == null) return "null"; + else return "[" + m.index + "]"; + } + + String substringUptoIndex(CharIndexed input) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < index; i++) { + sb.append(input.charAt(i)); + } + return sb.toString(); + } +*/ + +} diff --git a/gnu/java/util/regex/REMatchEnumeration.java b/gnu/java/util/regex/REMatchEnumeration.java new file mode 100644 index 000000000..bc700beac --- /dev/null +++ b/gnu/java/util/regex/REMatchEnumeration.java @@ -0,0 +1,135 @@ +/* gnu/regexp/REMatchEnumeration.java + Copyright (C) 1998-2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; +import java.io.Serializable; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * An REMatchEnumeration enumerates regular expression matches over a + * given input text. You obtain a reference to an enumeration using + * the <code>getMatchEnumeration()</code> methods on an instance of + * RE. + * + * <P> + * + * REMatchEnumeration does lazy computation; that is, it will not + * search for a match until it needs to. If you'd rather just get all + * the matches at once in a big array, use the + * <code>getAllMatches()</code> methods on RE. However, using an + * enumeration can help speed performance when the entire text does + * not need to be searched immediately. + * + * <P> + * + * The enumerated type is especially useful when searching on a Reader + * or InputStream, because the InputStream read position cannot be + * guaranteed after calling <code>getMatch()</code> (see the + * description of that method for an explanation of why). Enumeration + * also saves a lot of overhead required when calling + * <code>getMatch()</code> multiple times. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ +public class REMatchEnumeration implements Enumeration, Serializable { + private static final int YES = 1; + private static final int MAYBE = 0; + private static final int NO = -1; + + private int more; + private REMatch match; + private RE expr; + private CharIndexed input; + private int eflags; + private int index; + + // Package scope constructor is used by RE.getMatchEnumeration() + REMatchEnumeration(RE expr, CharIndexed input, int index, int eflags) { + more = MAYBE; + this.expr = expr; + this.input = input; + this.index = index; + this.eflags = eflags; + } + + /** Returns true if there are more matches in the input text. */ + public boolean hasMoreElements() { + return hasMoreMatches(null); + } + + /** Returns true if there are more matches in the input text. */ + public boolean hasMoreMatches() { + return hasMoreMatches(null); + } + + /** Returns true if there are more matches in the input text. + * Saves the text leading up to the match (or to the end of the input) + * in the specified buffer. + */ + public boolean hasMoreMatches(StringBuffer buffer) { + if (more == MAYBE) { + match = expr.getMatchImpl(input,index,eflags,buffer); + if (match != null) { + input.move((match.end[0] > 0) ? match.end[0] : 1); + + index = (match.end[0] > 0) ? match.end[0] + match.offset : index + 1; + more = YES; + } else more = NO; + } + return (more == YES); + } + + /** Returns the next match in the input text. */ + public Object nextElement() throws NoSuchElementException { + return nextMatch(); + } + + /** + * Returns the next match in the input text. This method is provided + * for convenience to avoid having to explicitly cast the return value + * to class REMatch. + */ + public REMatch nextMatch() throws NoSuchElementException { + if (hasMoreElements()) { + more = (input.isValid()) ? MAYBE : NO; + return match; + } + throw new NoSuchElementException(); + } +} + diff --git a/gnu/java/util/regex/RESyntax.java b/gnu/java/util/regex/RESyntax.java new file mode 100644 index 000000000..b66b32f58 --- /dev/null +++ b/gnu/java/util/regex/RESyntax.java @@ -0,0 +1,563 @@ +/* gnu/regexp/RESyntax.java + 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.util.regex; +import java.io.Serializable; +import java.util.BitSet; + +/** + * An RESyntax specifies the way a regular expression will be compiled. + * This class provides a number of predefined useful constants for + * emulating popular regular expression syntaxes. Additionally the + * user may construct his or her own syntax, using any combination of the + * syntax bit constants. The syntax is an optional argument to any of the + * matching methods on class RE. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + */ + +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 + private boolean isFinal = false; + + private String lineSeparator = DEFAULT_LINE_SEPARATOR; + + // Values for constants are bit indexes + + /** + * Syntax bit. Backslash is an escape character in lists. + */ + public static final int RE_BACKSLASH_ESCAPE_IN_LISTS = 0; + + /** + * Syntax bit. Use \? instead of ? and \+ instead of +. + */ + public static final int RE_BK_PLUS_QM = 1; + + /** + * Syntax bit. POSIX character classes ([:...:]) in lists are allowed. + */ + public static final int RE_CHAR_CLASSES = 2; + + /** + * Syntax bit. ^ and $ are special everywhere. + * <B>Not implemented.</B> + */ + public static final int RE_CONTEXT_INDEP_ANCHORS = 3; + + /** + * Syntax bit. Repetition operators are only special in valid positions. + * <B>Not implemented.</B> + */ + public static final int RE_CONTEXT_INDEP_OPS = 4; + + /** + * Syntax bit. Repetition and alternation operators are invalid + * at start and end of pattern and other places. + * <B>Not implemented</B>. + */ + public static final int RE_CONTEXT_INVALID_OPS = 5; + + /** + * Syntax bit. Match-any-character operator (.) matches a newline. + */ + public static final int RE_DOT_NEWLINE = 6; + + /** + * Syntax bit. Match-any-character operator (.) does not match a null. + */ + public static final int RE_DOT_NOT_NULL = 7; + + /** + * Syntax bit. Intervals ({x}, {x,}, {x,y}) are allowed. + */ + public static final int RE_INTERVALS = 8; + + /** + * Syntax bit. No alternation (|), match one-or-more (+), or + * match zero-or-one (?) operators. + */ + public static final int RE_LIMITED_OPS = 9; + + /** + * Syntax bit. Newline is an alternation operator. + */ + public static final int RE_NEWLINE_ALT = 10; // impl. + + /** + * Syntax bit. Intervals use { } instead of \{ \} + */ + public static final int RE_NO_BK_BRACES = 11; + + /** + * Syntax bit. Grouping uses ( ) instead of \( \). + */ + public static final int RE_NO_BK_PARENS = 12; + + /** + * Syntax bit. Backreferences not allowed. + */ + public static final int RE_NO_BK_REFS = 13; + + /** + * Syntax bit. Alternation uses | instead of \| + */ + public static final int RE_NO_BK_VBAR = 14; + + /** + * Syntax bit. <B>Not implemented</B>. + */ + public static final int RE_NO_EMPTY_RANGES = 15; + + /** + * Syntax bit. An unmatched right parenthesis (')' or '\)', depending + * on RE_NO_BK_PARENS) will throw an exception when compiling. + */ + public static final int RE_UNMATCHED_RIGHT_PAREN_ORD = 16; + + /** + * Syntax bit. <B>Not implemented.</B> + */ + public static final int RE_HAT_LISTS_NOT_NEWLINE = 17; + + /** + * Syntax bit. Stingy matching is allowed (+?, *?, ??, {x,y}?). + */ + public static final int RE_STINGY_OPS = 18; + + /** + * Syntax bit. Allow character class escapes (\d, \D, \s, \S, \w, \W). + */ + public static final int RE_CHAR_CLASS_ESCAPES = 19; + + /** + * Syntax bit. Allow use of (?:xxx) grouping (subexpression is not saved). + */ + public static final int RE_PURE_GROUPING = 20; + + /** + * Syntax bit. Allow use of (?=xxx) and (?!xxx) apply the subexpression + * to the text following the current position without consuming that text. + */ + public static final int RE_LOOKAHEAD = 21; + + /** + * Syntax bit. Allow beginning- and end-of-string anchors (\A, \Z). + */ + public static final int RE_STRING_ANCHORS = 22; + + /** + * Syntax bit. Allow embedded comments, (?#comment), as in Perl5. + */ + public static final int RE_COMMENTS = 23; + + /** + * Syntax bit. Allow character class escapes within lists, as in Perl5. + */ + public static final int RE_CHAR_CLASS_ESC_IN_LISTS = 24; + + /** + * Syntax bit. Possessive matching is allowed (++, *+, ?+, {x,y}+). + */ + public static final int RE_POSSESSIVE_OPS = 25; + + /** + * Syntax bit. Allow embedded flags, (?is-x), as in Perl5. + */ + public static final int RE_EMBEDDED_FLAGS = 26; + + /** + * Syntax bit. Allow octal char (\0377), as in Perl5. + */ + public static final int RE_OCTAL_CHAR = 27; + + /** + * Syntax bit. Allow hex char (\x1b), as in Perl5. + */ + public static final int RE_HEX_CHAR = 28; + + /** + * Syntax bit. Allow Unicode char (\u1234), as in Java 1.4. + */ + public static final int RE_UNICODE_CHAR = 29; + + /** + * Syntax bit. Allow named property (\p{P}, \P{p}), as in Perl5. + */ + public static final int RE_NAMED_PROPERTY = 30; + + /** + * Syntax bit. Allow nested characterclass ([a-z&&[^p-r]]), as in Java 1.4. + */ + public static final int RE_NESTED_CHARCLASS = 31; + + private static final int BIT_TOTAL = 32; + + /** + * Predefined syntax. + * Emulates regular expression support in the awk utility. + */ + public static final RESyntax RE_SYNTAX_AWK; + + /** + * Predefined syntax. + * Emulates regular expression support in the ed utility. + */ + public static final RESyntax RE_SYNTAX_ED; + + /** + * Predefined syntax. + * Emulates regular expression support in the egrep utility. + */ + public static final RESyntax RE_SYNTAX_EGREP; + + /** + * Predefined syntax. + * Emulates regular expression support in the GNU Emacs editor. + */ + public static final RESyntax RE_SYNTAX_EMACS; + + /** + * Predefined syntax. + * Emulates regular expression support in the grep utility. + */ + public static final RESyntax RE_SYNTAX_GREP; + + /** + * Predefined syntax. + * Emulates regular expression support in the POSIX awk specification. + */ + public static final RESyntax RE_SYNTAX_POSIX_AWK; + + /** + * Predefined syntax. + * Emulates POSIX basic regular expression support. + */ + public static final RESyntax RE_SYNTAX_POSIX_BASIC; + + /** + * Predefined syntax. + * Emulates regular expression support in the POSIX egrep specification. + */ + public static final RESyntax RE_SYNTAX_POSIX_EGREP; + + /** + * Predefined syntax. + * Emulates POSIX extended regular expression support. + */ + public static final RESyntax RE_SYNTAX_POSIX_EXTENDED; + + /** + * Predefined syntax. + * Emulates POSIX basic minimal regular expressions. + */ + public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_BASIC; + + /** + * Predefined syntax. + * Emulates POSIX extended minimal regular expressions. + */ + public static final RESyntax RE_SYNTAX_POSIX_MINIMAL_EXTENDED; + + /** + * Predefined syntax. + * Emulates regular expression support in the sed utility. + */ + public static final RESyntax RE_SYNTAX_SED; + + /** + * Predefined syntax. + * Emulates regular expression support in Larry Wall's perl, version 4, + */ + public static final RESyntax RE_SYNTAX_PERL4; + + /** + * Predefined syntax. + * Emulates regular expression support in Larry Wall's perl, version 4, + * using single line mode (/s modifier). + */ + public static final RESyntax RE_SYNTAX_PERL4_S; // single line mode (/s) + + /** + * Predefined syntax. + * Emulates regular expression support in Larry Wall's perl, version 5. + */ + public static final RESyntax RE_SYNTAX_PERL5; + + /** + * Predefined syntax. + * Emulates regular expression support in Larry Wall's perl, version 5, + * using single line mode (/s modifier). + */ + public static final RESyntax RE_SYNTAX_PERL5_S; + + /** + * Predefined syntax. + * Emulates regular expression support in Java 1.4's java.util.regex + * package. + */ + public static final RESyntax RE_SYNTAX_JAVA_1_4; + + static { + // Define syntaxes + + RE_SYNTAX_EMACS = new RESyntax().makeFinal(); + + RESyntax RE_SYNTAX_POSIX_COMMON = new RESyntax() + .set(RE_CHAR_CLASSES) + .set(RE_DOT_NEWLINE) + .set(RE_DOT_NOT_NULL) + .set(RE_INTERVALS) + .set(RE_NO_EMPTY_RANGES) + .makeFinal(); + + RE_SYNTAX_POSIX_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON) + .set(RE_BK_PLUS_QM) + .makeFinal(); + + RE_SYNTAX_POSIX_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON) + .set(RE_CONTEXT_INDEP_ANCHORS) + .set(RE_CONTEXT_INDEP_OPS) + .set(RE_NO_BK_BRACES) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_VBAR) + .set(RE_UNMATCHED_RIGHT_PAREN_ORD) + .makeFinal(); + + RE_SYNTAX_AWK = new RESyntax() + .set(RE_BACKSLASH_ESCAPE_IN_LISTS) + .set(RE_DOT_NOT_NULL) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_REFS) + .set(RE_NO_BK_VBAR) + .set(RE_NO_EMPTY_RANGES) + .set(RE_UNMATCHED_RIGHT_PAREN_ORD) + .makeFinal(); + + RE_SYNTAX_POSIX_AWK = new RESyntax(RE_SYNTAX_POSIX_EXTENDED) + .set(RE_BACKSLASH_ESCAPE_IN_LISTS) + .makeFinal(); + + RE_SYNTAX_GREP = new RESyntax() + .set(RE_BK_PLUS_QM) + .set(RE_CHAR_CLASSES) + .set(RE_HAT_LISTS_NOT_NEWLINE) + .set(RE_INTERVALS) + .set(RE_NEWLINE_ALT) + .makeFinal(); + + RE_SYNTAX_EGREP = new RESyntax() + .set(RE_CHAR_CLASSES) + .set(RE_CONTEXT_INDEP_ANCHORS) + .set(RE_CONTEXT_INDEP_OPS) + .set(RE_HAT_LISTS_NOT_NEWLINE) + .set(RE_NEWLINE_ALT) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_VBAR) + .makeFinal(); + + RE_SYNTAX_POSIX_EGREP = new RESyntax(RE_SYNTAX_EGREP) + .set(RE_INTERVALS) + .set(RE_NO_BK_BRACES) + .makeFinal(); + + /* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */ + + RE_SYNTAX_ED = new RESyntax(RE_SYNTAX_POSIX_BASIC) + .makeFinal(); + + RE_SYNTAX_SED = new RESyntax(RE_SYNTAX_POSIX_BASIC) + .makeFinal(); + + RE_SYNTAX_POSIX_MINIMAL_BASIC = new RESyntax(RE_SYNTAX_POSIX_COMMON) + .set(RE_LIMITED_OPS) + .makeFinal(); + + /* Differs from RE_SYNTAX_POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS + replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */ + + RE_SYNTAX_POSIX_MINIMAL_EXTENDED = new RESyntax(RE_SYNTAX_POSIX_COMMON) + .set(RE_CONTEXT_INDEP_ANCHORS) + .set(RE_CONTEXT_INVALID_OPS) + .set(RE_NO_BK_BRACES) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_REFS) + .set(RE_NO_BK_VBAR) + .set(RE_UNMATCHED_RIGHT_PAREN_ORD) + .makeFinal(); + + /* There is no official Perl spec, but here's a "best guess" */ + + RE_SYNTAX_PERL4 = new RESyntax() + .set(RE_BACKSLASH_ESCAPE_IN_LISTS) + .set(RE_CONTEXT_INDEP_ANCHORS) + .set(RE_CONTEXT_INDEP_OPS) // except for '{', apparently + .set(RE_INTERVALS) + .set(RE_NO_BK_BRACES) + .set(RE_NO_BK_PARENS) + .set(RE_NO_BK_VBAR) + .set(RE_NO_EMPTY_RANGES) + .set(RE_CHAR_CLASS_ESCAPES) // \d,\D,\w,\W,\s,\S + .makeFinal(); + + RE_SYNTAX_PERL4_S = new RESyntax(RE_SYNTAX_PERL4) + .set(RE_DOT_NEWLINE) + .makeFinal(); + + RE_SYNTAX_PERL5 = new RESyntax(RE_SYNTAX_PERL4) + .set(RE_PURE_GROUPING) // (?:) + .set(RE_STINGY_OPS) // *?,??,+?,{}? + .set(RE_LOOKAHEAD) // (?=)(?!) + .set(RE_STRING_ANCHORS) // \A,\Z + .set(RE_CHAR_CLASS_ESC_IN_LISTS)// \d,\D,\w,\W,\s,\S within [] + .set(RE_COMMENTS) // (?#) + .set(RE_EMBEDDED_FLAGS) // (?imsx-imsx) + .set(RE_OCTAL_CHAR) // \0377 + .set(RE_HEX_CHAR) // \x1b + .set(RE_NAMED_PROPERTY) // \p{prop}, \P{prop} + .makeFinal(); + + RE_SYNTAX_PERL5_S = new RESyntax(RE_SYNTAX_PERL5) + .set(RE_DOT_NEWLINE) + .makeFinal(); + + RE_SYNTAX_JAVA_1_4 = new RESyntax(RE_SYNTAX_PERL5) + // XXX + .set(RE_POSSESSIVE_OPS) // *+,?+,++,{}+ + .set(RE_UNICODE_CHAR) // \u1234 + .set(RE_NESTED_CHARCLASS) // [a-z&&[^p-r]] + .makeFinal(); + } + + /** + * Construct a new syntax object with all bits turned off. + * This is equivalent to RE_SYNTAX_EMACS. + */ + public RESyntax() { + bits = new BitSet(BIT_TOTAL); + } + + /** + * Called internally when constructing predefined syntaxes + * so their interpretation cannot vary. Conceivably useful + * for your syntaxes as well. Causes IllegalAccessError to + * be thrown if any attempt to modify the syntax is made. + * + * @return this object for convenient chaining + */ + public RESyntax makeFinal() { + isFinal = true; + return this; + } + + /** + * Construct a new syntax object with all bits set the same + * as the other syntax. + */ + public RESyntax(RESyntax other) { + bits = (BitSet) other.bits.clone(); + } + + /** + * Check if a given bit is set in this syntax. + */ + public boolean get(int index) { + return bits.get(index); + } + + /** + * Set a given bit in this syntax. + * + * @param index the constant (RESyntax.RE_xxx) bit to set. + * @return a reference to this object for easy chaining. + */ + public RESyntax set(int index) { + if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); + bits.set(index); + return this; + } + + /** + * Clear a given bit in this syntax. + * + * @param index the constant (RESyntax.RE_xxx) bit to clear. + * @return a reference to this object for easy chaining. + */ + public RESyntax clear(int index) { + if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); + bits.clear(index); + return this; + } + + /** + * Changes the line separator string for regular expressions + * created using this RESyntax. The default separator is the + * value returned by the system property "line.separator", which + * should be correct when reading platform-specific files from a + * filesystem. However, many programs may collect input from + * sources where the line separator is differently specified (for + * example, in the applet environment, the text box widget + * interprets line breaks as single-character newlines, + * regardless of the host platform. + * + * Note that setting the line separator to a character or + * characters that have specific meaning within the current syntax + * can cause unexpected chronosynclastic infundibula. + * + * @return this object for convenient chaining + */ + public RESyntax setLineSeparator(String aSeparator) { + if (isFinal) throw new IllegalAccessError(SYNTAX_IS_FINAL); + lineSeparator = aSeparator; + return this; + } + + /** + * Returns the currently active line separator string. The default + * is the platform-dependent system property "line.separator". + */ + public String getLineSeparator() { + return lineSeparator; + } +} diff --git a/gnu/java/util/regex/REToken.java b/gnu/java/util/regex/REToken.java new file mode 100644 index 000000000..155c01878 --- /dev/null +++ b/gnu/java/util/regex/REToken.java @@ -0,0 +1,189 @@ +/* gnu/regexp/REToken.java + 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.util.regex; +import java.io.Serializable; + +abstract class REToken implements Serializable, Cloneable { + + protected REToken next = null; + protected REToken uncle = null; + protected int subIndex; + protected boolean unicodeAware = true; + + public Object clone() { + try { + REToken copy = (REToken) super.clone(); + return copy; + } catch (CloneNotSupportedException e) { + throw new Error(); // doesn't happen + } + } + + protected REToken(int subIndex) { + this.subIndex = subIndex; + } + + int getMinimumLength() { + return 0; + } + + int getMaximumLength() { + return Integer.MAX_VALUE; + } + + void setUncle(REToken anUncle) { + uncle = anUncle; + } + + /** Returns true if the match succeeded, false if it failed. */ + boolean match(CharIndexed input, REMatch mymatch) { + REMatch m = matchThis(input, mymatch); + if (m == null) return false; + if (next(input, m)) { + mymatch.assignFrom(m); + return true; + } + return false; + } + + /** Returns true if the match succeeded, false if it failed. + * The matching is done against this REToken only. Chained + * tokens are not checked. + * This method is used to define the default match method. + * Simple subclasses of REToken, for example, such that + * matches only one character, should implement this method. + * Then the default match method will work. But complicated + * subclasses of REToken, which needs a special match method, + * do not have to implement this method. + */ + REMatch matchThis(CharIndexed input, REMatch mymatch) { + throw new UnsupportedOperationException( + "This REToken does not have a matchThis method"); + } + + /** Returns true if the rest of the tokens match, false if they fail. */ + protected boolean next(CharIndexed input, REMatch mymatch) { + REToken nextToken = getNext(); + if (nextToken == null) return true; + return nextToken.match(input, mymatch); + } + + /** Returns the next REToken chained to this REToken. */ + REToken getNext() { + return (next != null ? next : uncle); + } + + /** Finds a match at the position specified by the given REMatch. + * If necessary, adds a BacktrackStack.Backtrack object to backtrackStack + * of the REmatch found this time so that another possible match + * may be found when backtrack is called. + * By default, nothing is added to the backtrackStack. + * @param CharIndexed input Input character sequence. + * @param mymatch Position at which a match should be found + * @return REMatch object if a match was found, null otherwise. + */ + REMatch findMatch(CharIndexed input, REMatch mymatch) { + boolean b = match(input, mymatch); + if (b) return mymatch; + return null; + } + + boolean returnsFixedLengthMatches() { + return false; + } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + throw new UnsupportedOperationException( + "This token does not support findFixedLengthMatches"); + } + + /** + * Backtrack to another possibility. + * Ordinary REToken cannot do anything if this method is called. + */ + REMatch backtrack(CharIndexed input, REMatch mymatch, Object param) { + throw new IllegalStateException("This token cannot be backtracked to"); + } + + boolean chain(REToken token) { + next = token; + return true; // Token was accepted + } + + abstract void dump(StringBuffer os); + + void dumpAll(StringBuffer os) { + dump(os); + if (next != null) next.dumpAll(os); + } + + public String toString() { + StringBuffer os = new StringBuffer(); + dump(os); + return os.toString(); + } + + /** + * Converts the character argument to lowercase. + * @param ch the character to be converted. + * @param unicodeAware If true, use java.lang.Character#toLowerCase; + * otherwise, only US-ASCII charactes can be converted. + * @return the lowercase equivalent of the character, if any; + * otherwise, the character itself. + */ + public static char toLowerCase(char ch, boolean unicodeAware) { + if (unicodeAware) return Character.toLowerCase(ch); + if (ch >= 'A' && ch <= 'Z') return (char)(ch + 'a' - 'A'); + return ch; + } + + /** + * Converts the character argument to uppercase. + * @param ch the character to be converted. + * @param unicodeAware If true, use java.lang.Character#toUpperCase; + * otherwise, only US-ASCII charactes can be converted. + * @return the uppercase equivalent of the character, if any; + * otherwise, the character itself. + */ + public static char toUpperCase(char ch, boolean unicodeAware) { + if (unicodeAware) return Character.toUpperCase(ch); + if (ch >= 'a' && ch <= 'z') return (char)(ch + 'A' - 'a'); + return ch; + } + +} diff --git a/gnu/java/util/regex/RETokenAny.java b/gnu/java/util/regex/RETokenAny.java new file mode 100644 index 000000000..b99a54717 --- /dev/null +++ b/gnu/java/util/regex/RETokenAny.java @@ -0,0 +1,99 @@ +/* gnu/regexp/RETokenAny.java + 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.util.regex; + +final class RETokenAny extends REToken { + /** True if '.' can match a newline (RE_DOT_NEWLINE) */ + private boolean newline; + + /** True if '.' can't match a null (RE_DOT_NOT_NULL) */ + private boolean matchNull; + + RETokenAny(int subIndex, boolean newline, boolean matchNull) { + super(subIndex); + this.newline = newline; + this.matchNull = matchNull; + } + + int getMinimumLength() { + return 1; + } + + int getMaximumLength() { + return 1; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char ch = input.charAt(mymatch.index); + boolean retval = matchOneChar(ch); + if (retval) { + ++mymatch.index; + return mymatch; + } + return null; + } + + boolean matchOneChar(char ch) { + if ((ch == CharIndexed.OUT_OF_BOUNDS) + || (!newline && (ch == '\n')) + || (matchNull && (ch == 0))) { + return false; + } + return true; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + while (true) { + if (numRepeats >= max) break; + char ch = input.charAt(index++); + if (! matchOneChar(ch)) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + os.append('.'); + } +} + diff --git a/gnu/java/util/regex/RETokenBackRef.java b/gnu/java/util/regex/RETokenBackRef.java new file mode 100644 index 000000000..3a912f9bb --- /dev/null +++ b/gnu/java/util/regex/RETokenBackRef.java @@ -0,0 +1,86 @@ +/* gnu/regexp/RETokenBackRef.java + 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.util.regex; + +final class RETokenBackRef extends REToken { + private int num; + private boolean insens; + + RETokenBackRef(int subIndex, int num, boolean insens) { + super(subIndex); + this.num = num; + this.insens = insens; + } + + // should implement getMinimumLength() -- any ideas? + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + if (num >= mymatch.start.length) return null; + if (num >= mymatch.end.length) return null; + int b,e; + b = mymatch.start[num]; + e = mymatch.end[num]; + if ((b==-1)||(e==-1)) return null; // this shouldn't happen, but... + if (b < 0) b += 1; + if (e < 0) e += 1; + for (int i=b; i<e; i++) { + char c1 = input.charAt(mymatch.index+i-b); + char c2 = input.charAt(i); + if (c1 != c2) { + if (insens) { + if (c1 != toLowerCase(c2, unicodeAware) && + c1 != toUpperCase(c2, unicodeAware)) { + return null; + } + } + else { + return null; + } + } + } + mymatch.index += e-b; + return mymatch; + } + + void dump(StringBuffer os) { + os.append('\\').append(num); + } +} + + diff --git a/gnu/java/util/regex/RETokenChar.java b/gnu/java/util/regex/RETokenChar.java new file mode 100644 index 000000000..92d3efcf8 --- /dev/null +++ b/gnu/java/util/regex/RETokenChar.java @@ -0,0 +1,128 @@ +/* gnu/regexp/RETokenChar.java + 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.util.regex; + +final class RETokenChar extends REToken { + private char[] ch; + private boolean insens; + + RETokenChar(int subIndex, char c, boolean ins) { + super(subIndex); + insens = ins; + ch = new char [1]; + ch[0] = c; + } + + int getMinimumLength() { + return ch.length; + } + + int getMaximumLength() { + return ch.length; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + int z = ch.length; + if (matchOneString(input, mymatch.index)) { + mymatch.index += z; + return mymatch; + } + return null; + } + + boolean matchOneString(CharIndexed input, int index) { + int z = ch.length; + char c; + for (int i=0; i<z; i++) { + c = input.charAt(index+i); + if (! charEquals(c, ch[i])) { + return false; + } + } + return true; + } + + private boolean charEquals(char c1, char c2) { + if (c1 == c2) return true; + if (! insens) return false; + if (toLowerCase(c1, unicodeAware) == c2) return true; + if (toUpperCase(c1, unicodeAware) == c2) return true; + return false; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + int z = ch.length; + while (true) { + if (numRepeats >= max) break; + if (matchOneString(input, index)) { + index += z; + numRepeats++; + } + else break; + } + return numRepeats; + } + + // Overrides REToken.chain() to optimize for strings + boolean chain(REToken next) { + if (next instanceof RETokenChar && ((RETokenChar)next).insens == insens) { + RETokenChar cnext = (RETokenChar) next; + // assume for now that next can only be one character + int newsize = ch.length + cnext.ch.length; + + char[] chTemp = new char [newsize]; + + System.arraycopy(ch,0,chTemp,0,ch.length); + System.arraycopy(cnext.ch,0,chTemp,ch.length,cnext.ch.length); + + ch = chTemp; + return false; + } else return super.chain(next); + } + + void dump(StringBuffer os) { + os.append(ch); + } +} + + diff --git a/gnu/java/util/regex/RETokenEnd.java b/gnu/java/util/regex/RETokenEnd.java new file mode 100644 index 000000000..c846415e1 --- /dev/null +++ b/gnu/java/util/regex/RETokenEnd.java @@ -0,0 +1,89 @@ +/* gnu/regexp/RETokenEnd.java + 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.util.regex; + +final class RETokenEnd extends REToken { + /** + * Indicates whether this token should match on a line break. + */ + private String newline; + + RETokenEnd(int subIndex,String newline) { + super(subIndex); + this.newline = newline; + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char ch = input.charAt(mymatch.index); + if (ch == CharIndexed.OUT_OF_BOUNDS) + return ((mymatch.eflags & RE.REG_NOTEOL)>0) ? + null : mymatch; + if (newline != null) { + char z; + int i = 0; // position in newline + do { + z = newline.charAt(i); + if (ch != z) return null; + ++i; + ch = input.charAt(mymatch.index + i); + } while (i < newline.length()); + + return mymatch; + } + return null; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + REMatch m = (REMatch) mymatch.clone(); + REToken tk = (REToken) this.clone(); + tk.chain(null); + if (tk.match(input, m)) return max; + else return 0; + } + + void dump(StringBuffer os) { + os.append('$'); + } +} diff --git a/gnu/java/util/regex/RETokenEndOfPreviousMatch.java b/gnu/java/util/regex/RETokenEndOfPreviousMatch.java new file mode 100644 index 000000000..ea5580e16 --- /dev/null +++ b/gnu/java/util/regex/RETokenEndOfPreviousMatch.java @@ -0,0 +1,72 @@ +/* gnu/regexp/RETokenEndOfPreviousMatch.java + 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.util.regex; + +class RETokenEndOfPreviousMatch extends RETokenStart { + + RETokenEndOfPreviousMatch(int subIndex) { + super(subIndex, null); + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + REMatch lastMatch = input.getLastMatch(); + if (lastMatch == null) return super.matchThis(input, mymatch); + if (input.getAnchor()+mymatch.index == + lastMatch.anchor+lastMatch.index) { + return mymatch; + } + else { + return null; + } + } + + boolean returnsFixedLengthmatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + if (matchThis(input, mymatch) != null) return max; + else return 0; + } + + void dump(StringBuffer os) { + os.append("\\G"); + } +} diff --git a/gnu/java/util/regex/RETokenEndSub.java b/gnu/java/util/regex/RETokenEndSub.java new file mode 100644 index 000000000..57a146d03 --- /dev/null +++ b/gnu/java/util/regex/RETokenEndSub.java @@ -0,0 +1,66 @@ +/* gnu/regexp/RETokenEndSub.java + 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.util.regex; + +final class RETokenEndSub extends REToken { + RETokenEndSub(int subIndex) { + super(subIndex); + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + mymatch.start[subIndex] = mymatch.start1[subIndex]; + mymatch.end[subIndex] = mymatch.index; + return mymatch; + } + + REMatch findMatch(CharIndexed input, REMatch mymatch) { + mymatch.start[subIndex] = mymatch.start1[subIndex]; + mymatch.end[subIndex] = mymatch.index; + return super.findMatch(input, mymatch); + } + + void dump(StringBuffer os) { + // handled by RE + // But add something for debugging. + os.append("(?#RETokenEndSub subIndex=" + subIndex + ")"); + } +} diff --git a/gnu/java/util/regex/RETokenIndependent.java b/gnu/java/util/regex/RETokenIndependent.java new file mode 100644 index 000000000..48f865612 --- /dev/null +++ b/gnu/java/util/regex/RETokenIndependent.java @@ -0,0 +1,78 @@ +/* gnu/regexp/RETokenIndependent.java + 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.util.regex; + +/** + * @author Ito Kazumitsu + */ +final class RETokenIndependent extends REToken +{ + REToken re; + + RETokenIndependent(REToken re) throws REException { + super(0); + this.re = re; + } + + int getMinimumLength() { + return re.getMinimumLength(); + } + + int getMaximumLength() { + return re.getMaximumLength(); + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) + { + boolean b = re.match(input, mymatch); + if (b) { + // Once we have found a match, we do not see other possible matches. + if (mymatch.backtrackStack != null) mymatch.backtrackStack.clear(); + return mymatch; + + } + return null; + } + + void dump(StringBuffer os) { + os.append("(?>"); + re.dumpAll(os); + os.append(')'); + } +} + diff --git a/gnu/java/util/regex/RETokenLookAhead.java b/gnu/java/util/regex/RETokenLookAhead.java new file mode 100644 index 000000000..134f17c60 --- /dev/null +++ b/gnu/java/util/regex/RETokenLookAhead.java @@ -0,0 +1,80 @@ +/* gnu/regexp/RETokenLookAhead.java + 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.util.regex; + +/** + * @since gnu.regexp 1.1.3 + * @author Shashank Bapat + */ +final class RETokenLookAhead extends REToken +{ + REToken re; + boolean negative; + + RETokenLookAhead(REToken re, boolean negative) throws REException { + super(0); + this.re = re; + this.negative = negative; + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) + { + REMatch trymatch = (REMatch)mymatch.clone(); + if (re.match(input, trymatch)) { + if (negative) return null; + trymatch.index = mymatch.index; + return trymatch; + } + else { + if (negative) return mymatch; + return null; + } + } + + void dump(StringBuffer os) { + os.append("(?"); + os.append(negative ? '!' : '='); + re.dumpAll(os); + os.append(')'); + } +} + diff --git a/gnu/java/util/regex/RETokenLookBehind.java b/gnu/java/util/regex/RETokenLookBehind.java new file mode 100644 index 000000000..a01a15bc9 --- /dev/null +++ b/gnu/java/util/regex/RETokenLookBehind.java @@ -0,0 +1,118 @@ +/* gnu/regexp/RETokenLookBehind.java + 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.util.regex; + +/** + * @author Ito Kazumitsu + */ +final class RETokenLookBehind extends REToken +{ + REToken re; + boolean negative; + + RETokenLookBehind(REToken re, boolean negative) throws REException { + super(0); + this.re = re; + this.negative = negative; + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) + { + int max = re.getMaximumLength(); + CharIndexed behind = input.lookBehind(mymatch.index, max); + REMatch trymatch = (REMatch)mymatch.clone(); + REMatch trymatch1 = (REMatch)mymatch.clone(); + REMatch newMatch = null; + int diff = behind.length() - input.length(); + int curIndex = trymatch.index + diff; + trymatch.index = 0; + trymatch.offset = 0; + RETokenMatchHereOnly stopper = new RETokenMatchHereOnly(curIndex); + REToken re1 = (REToken) re.clone(); + re1.chain(stopper); + if (re1.match(behind, trymatch)) { + if (negative) return null; + for (int i = 0; i < trymatch.start.length; i++) { + if (trymatch.start[i] != -1 && trymatch.end[i] != -1) { + trymatch.start[i] -= diff; + if (trymatch.start[i] < 0) trymatch.start[i] -= 1; + trymatch.end[i] -= diff; + if (trymatch.end[i] < 0) trymatch.end[i] -= 1; + } + } + trymatch.index = mymatch.index; + trymatch.offset = mymatch.offset; + return trymatch; + } + else { + if (negative) return mymatch; + return null; + } + } + + void dump(StringBuffer os) { + os.append("(?<"); + os.append(negative ? '!' : '='); + re.dumpAll(os); + os.append(')'); + } + + private static class RETokenMatchHereOnly extends REToken { + + int getMaximumLength() { return 0; } + + private int index; + + RETokenMatchHereOnly(int index) { + super(0); + this.index = index; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + return (index == mymatch.index ? mymatch : null); + } + + void dump(StringBuffer os) {} + + } +} + diff --git a/gnu/java/util/regex/RETokenNamedProperty.java b/gnu/java/util/regex/RETokenNamedProperty.java new file mode 100644 index 000000000..a286c5be8 --- /dev/null +++ b/gnu/java/util/regex/RETokenNamedProperty.java @@ -0,0 +1,315 @@ +/* gnu/regexp/RETokenNamedProperty.java + 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.util.regex; + +final class RETokenNamedProperty extends REToken { + String name; + boolean insens; + boolean negate; + Handler handler; + + // Grouped properties + static final byte[] LETTER = new byte[] + { Character.LOWERCASE_LETTER, + Character.UPPERCASE_LETTER, + Character.TITLECASE_LETTER, + Character.MODIFIER_LETTER, + Character.OTHER_LETTER }; + + static final byte[] MARK = new byte[] + { Character.NON_SPACING_MARK, + Character.COMBINING_SPACING_MARK, + Character.ENCLOSING_MARK }; + + static final byte[] SEPARATOR = new byte[] + { Character.SPACE_SEPARATOR, + Character.LINE_SEPARATOR, + Character.PARAGRAPH_SEPARATOR }; + + static final byte[] SYMBOL = new byte[] + { Character.MATH_SYMBOL, + Character.CURRENCY_SYMBOL, + Character.MODIFIER_SYMBOL, + Character.OTHER_SYMBOL }; + + static final byte[] NUMBER = new byte[] + { Character.DECIMAL_DIGIT_NUMBER, + Character.LETTER_NUMBER, + Character.OTHER_NUMBER }; + + static final byte[] PUNCTUATION = new byte[] + { Character.DASH_PUNCTUATION, + Character.START_PUNCTUATION, + Character.END_PUNCTUATION, + Character.CONNECTOR_PUNCTUATION, + Character.OTHER_PUNCTUATION, + Character.INITIAL_QUOTE_PUNCTUATION, + Character.FINAL_QUOTE_PUNCTUATION}; + + static final byte[] OTHER = new byte[] + { Character.CONTROL, + Character.FORMAT, + Character.PRIVATE_USE, + Character.SURROGATE, + Character.UNASSIGNED }; + + RETokenNamedProperty(int subIndex, String name, boolean insens, boolean negate) throws REException { + super(subIndex); + this.name = name; + this.insens = insens; + this.negate = negate; + handler = getHandler(name); + } + + int getMinimumLength() { + return 1; + } + + int getMaximumLength() { + return 1; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char ch = input.charAt(mymatch.index); + boolean retval = matchOneChar(ch); + if (retval) { + ++mymatch.index; + return mymatch; + } + return null; + } + + private boolean matchOneChar(char ch) { + if (ch == CharIndexed.OUT_OF_BOUNDS) + return false; + + boolean retval = handler.includes(ch); + if (insens) { + retval = retval || + handler.includes(toUpperCase(ch, unicodeAware)) || + handler.includes(toLowerCase(ch, unicodeAware)); + } + + if (negate) retval = !retval; + return retval; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + while (true) { + if (numRepeats >= max) break; + char ch = input.charAt(index++); + if (! matchOneChar(ch)) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + os.append("\\") + .append(negate ? "P" : "p") + .append("{" + name + "}"); + } + + private abstract static class Handler { + public abstract boolean includes(char c); + } + + private Handler getHandler(String name) throws REException { + if (name.equals("Lower") || + name.equals("Upper") || + // name.equals("ASCII") || + name.equals("Alpha") || + name.equals("Digit") || + name.equals("Alnum") || + name.equals("Punct") || + name.equals("Graph") || + name.equals("Print") || + name.equals("Blank") || + name.equals("Cntrl") || + name.equals("XDigit") || + name.equals("Space") ) { + return new POSIXHandler(name); + } + if (name.startsWith("In")) { + try { + name = name.substring(2); + Character.UnicodeBlock block = Character.UnicodeBlock.forName(name); + return new UnicodeBlockHandler(block); + } + catch (IllegalArgumentException e) { + throw new REException("Invalid Unicode block name: " + name, REException.REG_ESCAPE, 0); + } + } + if (name.startsWith("Is")) { + name = name.substring(2); + } + + // "grouped properties" + if (name.equals("L")) + return new UnicodeCategoriesHandler(LETTER); + if (name.equals("M")) + return new UnicodeCategoriesHandler(MARK); + if (name.equals("Z")) + return new UnicodeCategoriesHandler(SEPARATOR); + if (name.equals("S")) + return new UnicodeCategoriesHandler(SYMBOL); + if (name.equals("N")) + return new UnicodeCategoriesHandler(NUMBER); + if (name.equals("P")) + return new UnicodeCategoriesHandler(PUNCTUATION); + if (name.equals("C")) + return new UnicodeCategoriesHandler(OTHER); + + if (name.equals("Mc")) + return new UnicodeCategoryHandler(Character.COMBINING_SPACING_MARK); + if (name.equals("Pc")) + return new UnicodeCategoryHandler(Character.CONNECTOR_PUNCTUATION); + if (name.equals("Cc")) + return new UnicodeCategoryHandler(Character.CONTROL); + if (name.equals("Sc")) + return new UnicodeCategoryHandler(Character.CURRENCY_SYMBOL); + if (name.equals("Pd")) + return new UnicodeCategoryHandler(Character.DASH_PUNCTUATION); + if (name.equals("Nd")) + return new UnicodeCategoryHandler(Character.DECIMAL_DIGIT_NUMBER); + if (name.equals("Me")) + return new UnicodeCategoryHandler(Character.ENCLOSING_MARK); + if (name.equals("Pe")) + return new UnicodeCategoryHandler(Character.END_PUNCTUATION); + if (name.equals("Pf")) + return new UnicodeCategoryHandler(Character.FINAL_QUOTE_PUNCTUATION); + if (name.equals("Cf")) + return new UnicodeCategoryHandler(Character.FORMAT); + if (name.equals("Pi")) + return new UnicodeCategoryHandler(Character.INITIAL_QUOTE_PUNCTUATION); + if (name.equals("Nl")) + return new UnicodeCategoryHandler(Character.LETTER_NUMBER); + if (name.equals("Zl")) + return new UnicodeCategoryHandler(Character.LINE_SEPARATOR); + if (name.equals("Ll")) + return new UnicodeCategoryHandler(Character.LOWERCASE_LETTER); + if (name.equals("Sm")) + return new UnicodeCategoryHandler(Character.MATH_SYMBOL); + if (name.equals("Lm")) + return new UnicodeCategoryHandler(Character.MODIFIER_LETTER); + if (name.equals("Sk")) + return new UnicodeCategoryHandler(Character.MODIFIER_SYMBOL); + if (name.equals("Mn")) + return new UnicodeCategoryHandler(Character.NON_SPACING_MARK); + if (name.equals("Lo")) + return new UnicodeCategoryHandler(Character.OTHER_LETTER); + if (name.equals("No")) + return new UnicodeCategoryHandler(Character.OTHER_NUMBER); + if (name.equals("Po")) + return new UnicodeCategoryHandler(Character.OTHER_PUNCTUATION); + if (name.equals("So")) + return new UnicodeCategoryHandler(Character.OTHER_SYMBOL); + if (name.equals("Zp")) + return new UnicodeCategoryHandler(Character.PARAGRAPH_SEPARATOR); + if (name.equals("Co")) + return new UnicodeCategoryHandler(Character.PRIVATE_USE); + if (name.equals("Zs")) + return new UnicodeCategoryHandler(Character.SPACE_SEPARATOR); + if (name.equals("Ps")) + return new UnicodeCategoryHandler(Character.START_PUNCTUATION); + if (name.equals("Cs")) + return new UnicodeCategoryHandler(Character.SURROGATE); + if (name.equals("Lt")) + return new UnicodeCategoryHandler(Character.TITLECASE_LETTER); + if (name.equals("Cn")) + return new UnicodeCategoryHandler(Character.UNASSIGNED); + if (name.equals("Lu")) + return new UnicodeCategoryHandler(Character.UPPERCASE_LETTER); + throw new REException("unsupported name " + name, REException.REG_ESCAPE, 0); + } + + private static class POSIXHandler extends Handler { + private RETokenPOSIX retoken; + public POSIXHandler(String name) { + int posixId = RETokenPOSIX.intValue(name.toLowerCase()); + if (posixId != -1) + retoken = new RETokenPOSIX(0,posixId,false,false); + else + throw new RuntimeException("Unknown posix ID: " + name); + } + public boolean includes(char c) { + return retoken.matchOneChar(c); + } + } + + private static class UnicodeCategoryHandler extends Handler { + public UnicodeCategoryHandler(byte category) { + this.category = (int)category; + } + private int category; + public boolean includes(char c) { + return Character.getType(c) == category; + } + } + + private static class UnicodeCategoriesHandler extends Handler { + public UnicodeCategoriesHandler(byte[] categories) { + this.categories = categories; + } + private byte[] categories; + public boolean includes(char c) { + int category = Character.getType(c); + for (int i = 0; i < categories.length; i++) + if (category == categories[i]) + return true; + return false; + } + } + + private static class UnicodeBlockHandler extends Handler { + public UnicodeBlockHandler(Character.UnicodeBlock block) { + this.block = block; + } + private Character.UnicodeBlock block; + public boolean includes(char c) { + Character.UnicodeBlock cblock = Character.UnicodeBlock.of(c); + return (cblock != null && cblock.equals(block)); + } + } + +} diff --git a/gnu/java/util/regex/RETokenOneOf.java b/gnu/java/util/regex/RETokenOneOf.java new file mode 100644 index 000000000..bccc78311 --- /dev/null +++ b/gnu/java/util/regex/RETokenOneOf.java @@ -0,0 +1,280 @@ +/* gnu/regexp/RETokenOneOf.java + 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.util.regex; +import java.util.Vector; +import java.util.Stack; + +final class RETokenOneOf extends REToken { + private Vector options; + private boolean negative; + // True if this RETokenOneOf is supposed to match only one character, + // which is typically the case of a character class expression. + private boolean matchesOneChar; + + private Vector addition; + // This Vector addition is used to store nested character classes. + // For example, if the original expression is + // [2-7a-c[f-k][m-z]&&[^p-v][st]] + // the basic part /2-7a-c/ is stored in the Vector options, and + // the additional part /[f-k][m-z]&&[^p-v][st]/ is stored in the + // Vector addition in the following order (Reverse Polish Notation): + // -- The matching result of the basic part is assumed here. + // [f-k] -- REToken + // "|" -- or + // [m-z] -- REToken + // "|" -- or + // false + // [^p-v] -- REToken + // "|" -- or + // [st] -- REToken + // "|" -- or + // "&" -- and + // + // As it is clear from the explanation above, the Vector addition is + // effective only when this REToken originates from a character class + // expression. + + // This constructor is used for convenience when we know the set beforehand, + // e.g. \d --> new RETokenOneOf("0123456789",false, ..) + // \D --> new RETokenOneOf("0123456789",true, ..) + + RETokenOneOf(int subIndex, String optionsStr, boolean negative, boolean insens) { + super(subIndex); + options = new Vector(); + this.negative = negative; + for (int i = 0; i < optionsStr.length(); i++) + options.addElement(new RETokenChar(subIndex,optionsStr.charAt(i),insens)); + matchesOneChar = true; + } + + RETokenOneOf(int subIndex, Vector options, boolean negative) { + super(subIndex); + this.options = options; + this.negative = negative; + matchesOneChar = negative; + } + + RETokenOneOf(int subIndex, Vector options, Vector addition, boolean negative) { + super(subIndex); + this.options = options; + this.addition = addition; + this.negative = negative; + matchesOneChar = (negative || addition != null); + } + + int getMinimumLength() { + if (matchesOneChar) return 1; + int min = Integer.MAX_VALUE; + int x; + for (int i=0; i < options.size(); i++) { + if ((x = ((REToken) options.elementAt(i)).getMinimumLength()) < min) + min = x; + } + return min; + } + + int getMaximumLength() { + if (matchesOneChar) return 1; + int max = 0; + int x; + for (int i=0; i < options.size(); i++) { + if ((x = ((REToken) options.elementAt(i)).getMaximumLength()) > max) + max = x; + } + return max; + } + + boolean match(CharIndexed input, REMatch mymatch) { + if (matchesOneChar) return matchOneChar(input, mymatch); + else return matchOneRE(input, mymatch); + } + + boolean matchOneChar(CharIndexed input, REMatch mymatch) { + REMatch tryMatch; + boolean tryOnly; + if (addition == null) { + tryMatch = mymatch; + tryOnly = false; + } + else { + tryMatch = (REMatch) mymatch.clone(); + tryOnly = true; + } + boolean b = negative ? + matchN(input, tryMatch, tryOnly) : + matchP(input, tryMatch, tryOnly); + if (addition == null) return b; + + Stack stack = new Stack(); + stack.push(new Boolean(b)); + for (int i=0; i < addition.size(); i++) { + Object obj = addition.elementAt(i); + if (obj instanceof REToken) { + b = ((REToken)obj).match(input, (REMatch)mymatch.clone()); + stack.push(new Boolean(b)); + } + else if (obj instanceof Boolean) { + stack.push(obj); + } + else if (obj.equals("|")) { + b = ((Boolean)stack.pop()).booleanValue(); + b = ((Boolean)stack.pop()).booleanValue() || b; + stack.push(new Boolean(b)); + } + else if (obj.equals("&")) { + b = ((Boolean)stack.pop()).booleanValue(); + b = ((Boolean)stack.pop()).booleanValue() && b; + stack.push(new Boolean(b)); + } + else { + throw new RuntimeException("Invalid object found"); + } + } + b = ((Boolean)stack.pop()).booleanValue(); + if (b) { + ++mymatch.index; + return next(input, mymatch); + } + return false; + } + + private boolean matchN(CharIndexed input, REMatch mymatch, boolean tryOnly) { + if (input.charAt(mymatch.index) == CharIndexed.OUT_OF_BOUNDS) + return false; + + REMatch newMatch = null; + REMatch last = null; + REToken tk; + for (int i=0; i < options.size(); i++) { + tk = (REToken) options.elementAt(i); + REMatch tryMatch = (REMatch) mymatch.clone(); + if (tk.match(input, tryMatch)) { // match was successful + return false; + } // is a match + } // try next option + + if (tryOnly) return true; + ++mymatch.index; + return next(input, mymatch); + } + + private boolean matchP(CharIndexed input, REMatch mymatch, boolean tryOnly) { + REToken tk; + for (int i=0; i < options.size(); i++) { + tk = (REToken) options.elementAt(i); + REMatch tryMatch = (REMatch) mymatch.clone(); + if (tk.match(input, tryMatch)) { // match was successful + if (tryOnly) return true; + if (next(input, tryMatch)) { + mymatch.assignFrom(tryMatch); + return true; + } + } + } + return false; + } + + private boolean matchOneRE(CharIndexed input, REMatch mymatch) { + REMatch newMatch = findMatch(input, mymatch); + if (newMatch != null) { + mymatch.assignFrom(newMatch); + return true; + } + return false; + } + + REMatch findMatch(CharIndexed input, REMatch mymatch) { + if (matchesOneChar) return super.findMatch(input, mymatch); + return findMatch(input, mymatch, 0); + } + + REMatch backtrack(CharIndexed input, REMatch mymatch, Object param) { + return findMatch(input, mymatch, ((Integer)param).intValue()); + } + + private REMatch findMatch(CharIndexed input, REMatch mymatch, int optionIndex) { + for (int i = optionIndex; i < options.size(); i++) { + REToken tk = (REToken) options.elementAt(i); + tk = (REToken) tk.clone(); + tk.chain(getNext()); + REMatch tryMatch = (REMatch) mymatch.clone(); + if (tryMatch.backtrackStack == null) { + tryMatch.backtrackStack = new BacktrackStack(); + } + boolean stackPushed = false; + if (i + 1 < options.size()) { + tryMatch.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, new Integer(i + 1))); + stackPushed = true; + } + boolean b = tk.match(input, tryMatch); + if (b) { + return tryMatch; + } + if (stackPushed) tryMatch.backtrackStack.pop(); + } + return null; + } + + boolean returnsFixedLengthMatches() { return matchesOneChar; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + if (!matchesOneChar) + return super.findFixedLengthMatches(input, mymatch, max); + int numRepeats = 0; + REMatch m = (REMatch) mymatch.clone(); + REToken tk = (REToken) this.clone(); + tk.chain(null); + while (true) { + if (numRepeats >= max) break; + m = tk.findMatch(input, m); + if (m == null) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + os.append(negative ? "[^" : "(?:"); + for (int i = 0; i < options.size(); i++) { + if (!negative && (i > 0)) os.append('|'); + ((REToken) options.elementAt(i)).dumpAll(os); + } + os.append(negative ? ']' : ')'); + } +} diff --git a/gnu/java/util/regex/RETokenPOSIX.java b/gnu/java/util/regex/RETokenPOSIX.java new file mode 100644 index 000000000..072989583 --- /dev/null +++ b/gnu/java/util/regex/RETokenPOSIX.java @@ -0,0 +1,167 @@ +/* gnu/regexp/RETokenPOSIX.java + 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.util.regex; + +final class RETokenPOSIX extends REToken { + int type; + boolean insens; + boolean negated; + + static final int ALNUM = 0; + static final int ALPHA = 1; + static final int BLANK = 2; + static final int CNTRL = 3; + static final int DIGIT = 4; + static final int GRAPH = 5; + static final int LOWER = 6; + static final int PRINT = 7; + static final int PUNCT = 8; + static final int SPACE = 9; + static final int UPPER = 10; + static final int XDIGIT = 11; + + // Array indices correspond to constants defined above. + static final String[] s_nameTable = { + "alnum", "alpha", "blank", "cntrl", "digit", "graph", "lower", + "print", "punct", "space", "upper", "xdigit" + }; + + // The RE constructor uses this to look up the constant for a string + static int intValue(String key) { + for (int i = 0; i < s_nameTable.length; i++) { + if (s_nameTable[i].equals(key)) return i; + } + return -1; + } + + RETokenPOSIX(int subIndex, int type, boolean insens, boolean negated) { + super(subIndex); + this.type = type; + this.insens = insens; + this.negated = negated; + } + + int getMinimumLength() { + return 1; + } + + int getMaximumLength() { + return 1; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char ch = input.charAt(mymatch.index); + boolean retval = matchOneChar(ch); + if (retval) { + ++mymatch.index; + return mymatch; + } + return null; + } + + boolean matchOneChar(char ch) { + if (ch == CharIndexed.OUT_OF_BOUNDS) + return false; + + boolean retval = false; + switch (type) { + case ALNUM: + // Note that there is some debate over whether '_' should be included + retval = Character.isLetterOrDigit(ch) || (ch == '_'); + break; + case ALPHA: + retval = Character.isLetter(ch); + break; + case BLANK: + retval = ((ch == ' ') || (ch == '\t')); + break; + case CNTRL: + retval = Character.isISOControl(ch); + break; + case DIGIT: + retval = Character.isDigit(ch); + break; + case GRAPH: + retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch))); + break; + case LOWER: + retval = ((insens && Character.isLetter(ch)) || Character.isLowerCase(ch)); + break; + case PRINT: + retval = (!(Character.isWhitespace(ch) || Character.isISOControl(ch))) + || (ch == ' '); + break; + case PUNCT: + // This feels sloppy, especially for non-U.S. locales. + retval = ("`~!@#$%^&*()-_=+[]{}\\|;:'\"/?,.<>".indexOf(ch)!=-1); + break; + case SPACE: + retval = Character.isWhitespace(ch); + break; + case UPPER: + retval = ((insens && Character.isLetter(ch)) || Character.isUpperCase(ch)); + break; + case XDIGIT: + retval = (Character.isDigit(ch) || ("abcdefABCDEF".indexOf(ch)!=-1)); + break; + } + + if (negated) retval = !retval; + return retval; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + while (true) { + if (numRepeats >= max) break; + char ch = input.charAt(index++); + if (! matchOneChar(ch)) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + if (negated) os.append('^'); + os.append("[:" + s_nameTable[type] + ":]"); + } +} diff --git a/gnu/java/util/regex/RETokenRange.java b/gnu/java/util/regex/RETokenRange.java new file mode 100644 index 000000000..8a65f77f1 --- /dev/null +++ b/gnu/java/util/regex/RETokenRange.java @@ -0,0 +1,100 @@ +/* gnu/regexp/RETokenRange.java + 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.util.regex; + +final class RETokenRange extends REToken { + private char lo, hi; + private boolean insens; + + RETokenRange(int subIndex, char lo, char hi, boolean ins) { + super(subIndex); + insens = ins; + this.lo = lo; + this.hi = hi; + } + + int getMinimumLength() { + return 1; + } + + int getMaximumLength() { + return 1; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + char c = input.charAt(mymatch.index); + if (matchOneChar(c)) { + ++mymatch.index; + return mymatch; + } + return null; + } + + boolean matchOneChar(char c) { + if (c == CharIndexed.OUT_OF_BOUNDS) return false; + boolean matches = (c >= lo) && (c <= hi); + if (! matches && insens) { + char c1 = toLowerCase(c, unicodeAware); + matches = (c1 >= lo) && (c1 <= hi); + if (!matches) { + c1 = toUpperCase(c, unicodeAware); + matches = (c1 >= lo) && (c1 <= hi); + } + } + return matches; + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + int index = mymatch.index; + int numRepeats = 0; + while (true) { + if (numRepeats >= max) break; + char ch = input.charAt(index++); + if (! matchOneChar(ch)) break; + numRepeats++; + } + return numRepeats; + } + + void dump(StringBuffer os) { + os.append(lo).append('-').append(hi); + } +} + diff --git a/gnu/java/util/regex/RETokenRepeated.java b/gnu/java/util/regex/RETokenRepeated.java new file mode 100644 index 000000000..531c4a311 --- /dev/null +++ b/gnu/java/util/regex/RETokenRepeated.java @@ -0,0 +1,427 @@ +/* gnu/regexp/RETokenRepeated.java + 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.util.regex; + +// import java.util.Vector; +// import java.util.Stack; + +final class RETokenRepeated extends REToken { + private REToken token; + private int min,max; + private boolean stingy; + private boolean possessive; + private int tokenFixedLength; + + RETokenRepeated(int subIndex, REToken token, int min, int max) { + super(subIndex); + this.token = token; + this.min = min; + this.max = max; + if (token.returnsFixedLengthMatches()) { + tokenFixedLength = token.getMaximumLength(); + } + else { + tokenFixedLength = -1; + } + } + + /** Sets the minimal matching mode to true. */ + void makeStingy() { + stingy = true; + } + + /** Queries if this token has minimal matching enabled. */ + boolean isStingy() { + return stingy; + } + + /** Sets possessive matching mode to true. */ + void makePossessive() { + possessive = true; + } + + /** Queries if this token has possessive matching enabled. */ + boolean isPossessive() { + return possessive; + } + + /** + * The minimum length of a repeated token is the minimum length + * of the token multiplied by the minimum number of times it must + * match. + */ + int getMinimumLength() { + return (min * token.getMinimumLength()); + } + + int getMaximumLength() { + if (max == Integer.MAX_VALUE) return Integer.MAX_VALUE; + int tmax = token.getMaximumLength(); + if (tmax == Integer.MAX_VALUE) return tmax; + return (max * tmax); + } + + // The comment "MUST make a clone" below means that some tests + // failed without doing clone(), + + private static class DoablesFinder { + private REToken tk; + private CharIndexed input; + private REMatch rematch; + private boolean findFirst; + + private DoablesFinder(REToken tk, CharIndexed input, REMatch mymatch) { + this.tk = tk; + this.input = input; + this.rematch = (REMatch) mymatch.clone(); // MUST make a clone + this.rematch.backtrackStack = new BacktrackStack(); + findFirst = true; + } + + private REMatch find() { + int origin = rematch.index; + REMatch rem; + if (findFirst) { + rem = tk.findMatch(input, rematch); + findFirst = false; + } + else { + while (true) { + if (rematch.backtrackStack.empty()) { + rem = null; + break; + } + BacktrackStack.Backtrack bt = rematch.backtrackStack.pop(); + rem = bt.token.backtrack(bt.input, bt.match, bt.param); + if (rem != null) break; + } + } + if (rem == null) return null; + if (rem.index == origin) rem.empty = true; + rematch = rem; + return (REMatch) rem.clone(); // MUST make a clone. + } + + boolean noMore() { + return rematch.backtrackStack.empty(); + } + } + + REMatch findMatch(CharIndexed input, REMatch mymatch) { + if (tokenFixedLength >= 0) return findMatchFixedLength(input, mymatch); + BacktrackStack stack = new BacktrackStack(); + stack.push(new StackedInfo(input, 0, mymatch, null, null)); + return findMatch(stack); + } + + REMatch backtrack(CharIndexed input, REMatch mymatch, Object param) { + if (tokenFixedLength >= 0) return backtrackFixedLength(input, mymatch, param); + return findMatch((BacktrackStack)param); + } + + private static class StackedInfo extends BacktrackStack.Backtrack { + int numRepeats; + int[] visited; + DoablesFinder finder; + StackedInfo(CharIndexed input, int numRepeats, REMatch match, + int[] visited, DoablesFinder finder) { + super(null, input, match, null); + this.numRepeats = numRepeats; + this.visited = visited; + this.finder = finder; + } + } + + private REMatch findMatch(BacktrackStack stack) { + // Avoid using recursive calls. + MAIN_LOOP: + while (true) { + + if (stack.empty()) return null; + StackedInfo si = (StackedInfo)(stack.peek()); + CharIndexed input = si.input; + int numRepeats = si.numRepeats; + REMatch mymatch = si.match; + int[] visited = si.visited; + DoablesFinder finder = si.finder; + + if (mymatch.backtrackStack == null) + mymatch.backtrackStack = new BacktrackStack(); + + if (numRepeats >= max) { + stack.pop(); + REMatch m1 = matchRest(input, mymatch); + if (m1 != null) { + if (! stack.empty()) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + } + return m1; + } + if (stingy) { + continue MAIN_LOOP; + } + return null; + } + + if (finder == null) { + finder = new DoablesFinder(token, input, mymatch); + si.finder = finder; + } + + if (numRepeats < min) { + while (true) { + REMatch doable = finder.find(); + if (doable == null) { + if (stack.empty()) return null; + stack.pop(); + continue MAIN_LOOP; + } + if (finder.noMore()) stack.pop(); + int newNumRepeats = (doable.empty ? min : numRepeats + 1); + stack.push(new StackedInfo( + input, newNumRepeats, doable, visited, null)); + continue MAIN_LOOP; + } + } + + if (visited == null) visited = initVisited(); + + if (stingy) { + REMatch nextMatch = finder.find(); + if (nextMatch != null && !nextMatch.empty) { + stack.push(new StackedInfo( + input, numRepeats + 1, nextMatch, visited, null)); + } + else { + stack.pop(); + } + REMatch m1 = matchRest(input, mymatch); + if (m1 != null) { + if (!stack.empty()) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + } + return m1; + } + else { + continue MAIN_LOOP; + } + } + + visited = addVisited(mymatch.index, visited); + + DO_THIS: + do { + + boolean emptyMatchFound = false; + + DO_ONE_DOABLE: + while (true) { + + REMatch doable = finder.find(); + if (doable == null) { + break DO_THIS; + } + if (doable.empty) emptyMatchFound = true; + + if (!emptyMatchFound) { + int n = doable.index; + if (! visitedContains(n, visited)) { + visited = addVisited(n, visited); + } + else { + continue DO_ONE_DOABLE; + } + stack.push(new StackedInfo( + input, numRepeats + 1, doable, visited, null)); + REMatch m1 = findMatch(stack); + if (possessive) { + return m1; + } + if (m1 != null) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + return m1; + } + } + else { + REMatch m1 = matchRest(input, doable); + if (possessive) { + return m1; + } + if (m1 != null) { + if (! stack.empty()) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + } + return m1; + } + } + + } // DO_ONE_DOABLE + + } while (false); // DO_THIS only once; + + if (!stack.empty()) { + stack.pop(); + } + if (possessive) { + stack.clear(); + } + REMatch m1 = matchRest(input, mymatch); + if (m1 != null) { + if (! stack.empty()) { + m1.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, stack)); + } + return m1; + } + + } // MAIN_LOOP + } + + boolean match(CharIndexed input, REMatch mymatch) { + REMatch m1 = findMatch(input, mymatch); + if (m1 != null) { + mymatch.assignFrom(m1); + return true; + } + return false; + } + + // Array visited is an array of character positions we have already + // visited. visited[0] is used to store the effective length of the + // array. + private static int[] initVisited() { + int[] visited = new int[32]; + visited[0] = 0; + return visited; + } + + private static boolean visitedContains(int n, int[] visited) { + // Experience tells that for a small array like this, + // simple linear search is faster than binary search. + for (int i = 1; i < visited[0]; i++) { + if (n == visited[i]) return true; + } + return false; + } + + private static int[] addVisited(int n, int[] visited) { + if (visitedContains(n, visited)) return visited; + if (visited[0] >= visited.length - 1) { + int[] newvisited = new int[visited.length + 32]; + System.arraycopy(visited, 0, newvisited, 0, visited.length); + visited = newvisited; + } + visited[0]++; + visited[visited[0]] = n; + return visited; + } + + private REMatch matchRest(CharIndexed input, final REMatch newMatch) { + if (next(input, newMatch)) { + return newMatch; + } + return null; + } + + private REMatch findMatchFixedLength(CharIndexed input, REMatch mymatch) { + if (mymatch.backtrackStack == null) + mymatch.backtrackStack = new BacktrackStack(); + int numRepeats = token.findFixedLengthMatches(input, (REMatch)mymatch.clone(), max); + if (numRepeats == Integer.MAX_VALUE) numRepeats = min; + int count = numRepeats - min + 1; + if (count <= 0) return null; + int index = 0; + if (!stingy) index = mymatch.index + (tokenFixedLength * numRepeats); + else index = mymatch.index + (tokenFixedLength * min); + return findMatchFixedLength(input, mymatch, index, count); + } + + private REMatch backtrackFixedLength(CharIndexed input, REMatch mymatch, + Object param) { + int[] params = (int[])param; + int index = params[0]; + int count = params[1]; + return findMatchFixedLength(input, mymatch, index, count); + } + + private REMatch findMatchFixedLength(CharIndexed input, REMatch mymatch, + int index, int count) { + REMatch tryMatch = (REMatch) mymatch.clone(); + while (true) { + tryMatch.index = index; + REMatch m = matchRest(input, tryMatch); + count--; + if (stingy) index += tokenFixedLength; + else index -= tokenFixedLength; + if (possessive) return m; + if (m != null) { + if (count > 0) { + m.backtrackStack.push(new BacktrackStack.Backtrack( + this, input, mymatch, + new int[] {index, count})); + } + return m; + } + if (count <= 0) return null; + } + } + + void dump(StringBuffer os) { + os.append("(?:"); + token.dumpAll(os); + os.append(')'); + if ((max == Integer.MAX_VALUE) && (min <= 1)) + os.append( (min == 0) ? '*' : '+' ); + else if ((min == 0) && (max == 1)) + os.append('?'); + else { + os.append('{').append(min); + if (max > min) { + os.append(','); + if (max != Integer.MAX_VALUE) os.append(max); + } + os.append('}'); + } + if (stingy) os.append('?'); + } +} diff --git a/gnu/java/util/regex/RETokenStart.java b/gnu/java/util/regex/RETokenStart.java new file mode 100644 index 000000000..56bc36129 --- /dev/null +++ b/gnu/java/util/regex/RETokenStart.java @@ -0,0 +1,98 @@ +/* gnu/regexp/RETokenStart.java + 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.util.regex; + +class RETokenStart extends REToken { + private String newline; // matches after a newline + + RETokenStart(int subIndex, String newline) { + super(subIndex); + this.newline = newline; + } + + int getMaximumLength() { + return 0; + } + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + // charAt(index-n) may be unknown on a Reader/InputStream. FIXME + // Match after a newline if in multiline mode + + if (newline != null) { + int len = newline.length(); + if (mymatch.offset >= len) { + boolean found = true; + char z; + int i = 0; // position in REToken.newline + char ch = input.charAt(mymatch.index - len); + do { + z = newline.charAt(i); + if (ch != z) { + found = false; + break; + } + ++i; + ch = input.charAt(mymatch.index - len + i); + } while (i < len); + + if (found) return mymatch; + } + } + + // Don't match at all if REG_NOTBOL is set. + if ((mymatch.eflags & RE.REG_NOTBOL) > 0) return null; + + if ((mymatch.eflags & RE.REG_ANCHORINDEX) > 0) + return (mymatch.anchor == mymatch.offset) ? + mymatch : null; + else + return ((mymatch.index == 0) && (mymatch.offset == 0)) ? + mymatch : null; + } + + boolean returnsFixedLengthmatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + if (matchThis(input, mymatch) != null) return max; + else return 0; + } + + void dump(StringBuffer os) { + os.append('^'); + } +} diff --git a/gnu/java/util/regex/RETokenWordBoundary.java b/gnu/java/util/regex/RETokenWordBoundary.java new file mode 100644 index 000000000..538c6bef4 --- /dev/null +++ b/gnu/java/util/regex/RETokenWordBoundary.java @@ -0,0 +1,116 @@ +/* gnu/regexp/RETokenWordBoundary.java + 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.util.regex; + +/** + * Represents a combination lookahead/lookbehind for POSIX [:alnum:]. + */ +final class RETokenWordBoundary extends REToken { + private boolean negated; + private int where; + static final int BEGIN = 1; + static final int END = 2; + + RETokenWordBoundary(int subIndex, int where, boolean negated) { + super(subIndex); + this.where = where; + this.negated = negated; + } + + int getMaximumLength() { + return 0; + } + + + REMatch matchThis(CharIndexed input, REMatch mymatch) { + // Word boundary means input[index-1] was a word character + // and input[index] is not, or input[index] is a word character + // and input[index-1] was not + // In the string "one two three", these positions match: + // |o|n|e| |t|w|o| |t|h|r|e|e| + // ^ ^ ^ ^ ^ ^ + boolean after = false; // is current character a letter or digit? + boolean before = false; // is previous character a letter or digit? + char ch; + + // TODO: Also check REG_ANCHORINDEX vs. anchor + if (((mymatch.eflags & RE.REG_ANCHORINDEX) != RE.REG_ANCHORINDEX) + || (mymatch.offset + mymatch.index > mymatch.anchor)) { + if ((ch = input.charAt(mymatch.index - 1)) != CharIndexed.OUT_OF_BOUNDS) { + before = Character.isLetterOrDigit(ch) || (ch == '_'); + } + } + + if ((ch = input.charAt(mymatch.index)) != CharIndexed.OUT_OF_BOUNDS) { + after = Character.isLetterOrDigit(ch) || (ch == '_'); + } + + // if (before) and (!after), we're at end (\>) + // if (after) and (!before), we're at beginning (\<) + boolean doNext = false; + + if ((where & BEGIN) == BEGIN) { + doNext = after && !before; + } + if ((where & END) == END) { + doNext ^= before && !after; + } + + if (negated) doNext = !doNext; + + return (doNext ? mymatch : null); + } + + boolean returnsFixedLengthMatches() { return true; } + + int findFixedLengthMatches(CharIndexed input, REMatch mymatch, int max) { + if(matchThis(input, mymatch) != null) return max; + else return 0; + } + + void dump(StringBuffer os) { + if (where == (BEGIN | END)) { + os.append( negated ? "\\B" : "\\b" ); + } else if (where == BEGIN) { + os.append("\\<"); + } else { + os.append("\\>"); + } + } +} diff --git a/gnu/java/util/regex/UncheckedRE.java b/gnu/java/util/regex/UncheckedRE.java new file mode 100644 index 000000000..73a67c657 --- /dev/null +++ b/gnu/java/util/regex/UncheckedRE.java @@ -0,0 +1,109 @@ +/* gnu/regexp/UncheckedRE.java + Copyright (C) 2001, 2004 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.java.util.regex; + +/** + * UncheckedRE is a subclass of RE that allows programmers an easier means + * of programmatically precompiling regular expressions. It is constructed + * and used in exactly the same manner as an instance of the RE class; the + * only difference is that its constructors do not throw REException. + * Instead, if a syntax error is encountered during construction, a + * RuntimeException will be thrown. + * <P> + * Note that this makes UncheckedRE dangerous if constructed with + * dynamic data. Do not use UncheckedRE unless you are completely sure + * that all input being passed to it contains valid, well-formed + * regular expressions for the syntax specified. + * + * @author <A HREF="mailto:wes@cacas.org">Wes Biggs</A> + * @see gnu.java.util.regex.RE + * @since gnu.regexp 1.1.4 + */ + +public final class UncheckedRE extends RE { + /** + * Constructs a regular expression pattern buffer without any compilation + * flags set, and using the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer or char[]. Other input types will be converted to + * strings using the toString() method. + * @exception RuntimeException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public UncheckedRE(Object pattern) { + this(pattern,0,RESyntax.RE_SYNTAX_PERL5); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and the default syntax (RESyntax.RE_SYNTAX_PERL5). + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags in the RE class. + * @exception RuntimeException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public UncheckedRE(Object pattern, int cflags) { + this(pattern,cflags,RESyntax.RE_SYNTAX_PERL5); + } + + /** + * Constructs a regular expression pattern buffer using the specified + * compilation flags and regular expression syntax. + * + * @param pattern A regular expression pattern, in the form of a String, + * StringBuffer, or char[]. Other input types will be converted to + * strings using the toString() method. + * @param cflags The logical OR of any combination of the compilation flags in the RE class. + * @param syntax The type of regular expression syntax to use. + * @exception RuntimeException The input pattern could not be parsed. + * @exception NullPointerException The pattern was null. + */ + public UncheckedRE(Object pattern, int cflags, RESyntax syntax) { + try { + initialize(pattern,cflags,syntax,0,0); + } catch (REException e) { + throw new RuntimeException(e.getMessage()); + } + } +} + + |