Description: Fix class visibility
Origin: Piccolo2d
Last-Update: 2008-07-30 15:05:56 +0200

Reduced visibility of several members, in order to make more implementation 
details publicly invisible.

At least the dependencies to sun.* should be a private implementation detail.

http://code.google.com/p/piccolo2d/issues/detail?id=48


--- a/extras/edu/umd/cs/piccolox/util/PFixedWidthStroke.java
+++ b/extras/edu/umd/cs/piccolox/util/PFixedWidthStroke.java
@@ -1,395 +1,259 @@
-/*
- * Copyright (c) 2002-@year@, University of Maryland
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification, are permitted provided
- * that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this list of conditions
- * and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
- * and the following disclaimer in the documentation and/or other materials provided with the
- * distribution.
- *
- * Neither the name of the University of Maryland nor the names of its contributors may be used to
- * endorse or promote products derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
- * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * Piccolo was written at the Human-Computer Interaction Laboratory www.cs.umd.edu/hcil by Jesse Grosjean
- * under the supervision of Ben Bederson. The Piccolo website is www.cs.umd.edu/hcil/piccolo.
- */
-package edu.umd.cs.piccolox.util;
-
-import java.awt.Shape;
-import java.awt.Stroke;
-import java.awt.geom.GeneralPath;
-import java.awt.geom.PathIterator;
-import java.awt.geom.Rectangle2D;
-import java.io.Serializable;
-
-import sun.dc.path.FastPathProducer;
-import sun.dc.path.PathConsumer;
-import sun.dc.path.PathException;
-import sun.dc.pr.PathDasher;
-import sun.dc.pr.PathStroker;
-import sun.dc.pr.Rasterizer;
-
-import edu.umd.cs.piccolo.util.PAffineTransform;
-import edu.umd.cs.piccolo.util.PDebug;
-import edu.umd.cs.piccolo.util.PPaintContext;
-import edu.umd.cs.piccolo.util.PPickPath;
-
-/**
- * <b>PFixedWidthStroke</b> is the same as java.awt.BasicStroke except that PFixedWidthStroke
- * has a fixed width on the screen so that even when the canvas view is zooming its
- * width stays the same in canvas coordinates. Note that this stroke draws in the inside of 
- * the stroked shape, instead of the normal draw on center behavior.
- * <P>
- * @see edu.umd.cs.piccolo.nodes.PPath
- * @version 1.0
- * @author Jesse Grosjean
- */
-public class PFixedWidthStroke implements Stroke, Serializable {
-	
-	private static PAffineTransform TEMP_TRANSFORM = new PAffineTransform();
-	private static GeneralPath TEMP_PATH = new GeneralPath(GeneralPath.WIND_NON_ZERO);
-	
-	public final static int JOIN_MITER = 0;
-	public final static int JOIN_ROUND = 1;
-	public final static int JOIN_BEVEL = 2;
-	public final static int CAP_BUTT = 0;
-	public final static int CAP_ROUND = 1;
-	public final static int CAP_SQUARE = 2;
-
-	private float width;
-	private int join;
-	private int cap;
-	private float miterlimit;
-	private float dash[];
-	private float dash_phase;
-
-	public static final int RasterizerCaps[] = {
-		Rasterizer.BUTT, Rasterizer.ROUND, Rasterizer.SQUARE
-	};
-
-	public static final int RasterizerCorners[] = {
-		Rasterizer.MITER, Rasterizer.ROUND, Rasterizer.BEVEL
-	};
-
-	public class FillAdapter implements PathConsumer {
-		boolean closed;
-		GeneralPath path;
-
-		public FillAdapter() {
-			path = TEMP_PATH;
-			path.reset();
-		}
-
-		public Shape getShape() {
-			return path;
-		}
-
-		public void beginPath() {}
-
-		public void beginSubpath(float x0, float y0) {
-			if (closed) {
-				path.closePath();
-				closed = false;
-			}
-			path.moveTo(x0, y0);
-		}
-
-		public void appendLine(float x1, float y1) {
-			path.lineTo(x1, y1);
-		}
-
-		public void appendQuadratic(float xm, float ym, float x1, float y1) {
-			path.quadTo(xm, ym, x1, y1);
-		}
-
-		public void appendCubic(float xm, float ym,
-					float xn, float yn,
-					float x1, float y1) {
-			path.curveTo(xm, ym, xn, yn, x1, y1);
-		}
-
-		public void closedSubpath() {
-			closed = true;
-		}
-
-		public void endPath() {
-			if (closed) {
-				path.closePath();
-				closed = false;
-			}
-		}
-
-		public void useProxy(FastPathProducer proxy)
-			throws PathException {
-			proxy.sendTo(this);
-		}
-
-		public long getCPathConsumer() {
-			return 0;
-		}
-		
-		public void dispose() {
-		}
-
-		public PathConsumer getConsumer() {
-			return null;
-		}
-	}
-	
-	public PFixedWidthStroke() {
-		this(1.0f, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
-	}
-	
-	public PFixedWidthStroke(float width) {
-		this(width, CAP_SQUARE, JOIN_MITER, 10.0f, null, 0.0f);
-	}
-	
-	public PFixedWidthStroke(float width, int cap, int join) {
-		this(width, cap, join, 10.0f, null, 0.0f);
-	}
-	
-	public PFixedWidthStroke(float width, int cap, int join, float miterlimit) {
-		this(width, cap, join, miterlimit, null, 0.0f);
-	}
-	
-	public PFixedWidthStroke(float width, int cap, int join, float miterlimit, float dash[], float dash_phase) {
-		if (width < 0.0f) {
-			throw new IllegalArgumentException("negative width");
-		}
-		if (cap != CAP_BUTT && cap != CAP_ROUND && cap != CAP_SQUARE) {
-			throw new IllegalArgumentException("illegal end cap value");
-		}
-		if (join == JOIN_MITER) {
-			if (miterlimit < 1.0f) {
-				throw new IllegalArgumentException("miter limit < 1");
-			}
-		} else if (join != JOIN_ROUND && join != JOIN_BEVEL) {
-			throw new IllegalArgumentException("illegal line join value");
-		}
-		if (dash != null) {
-			if (dash_phase < 0.0f) {
-				throw new IllegalArgumentException("negative dash phase");
-			}
-			boolean allzero = true;
-			for (int i = 0; i < dash.length; i++) {
-				float d = dash[i];
-				if (d > 0.0) {
-					allzero = false;
-				} else if (d < 0.0) {
-					throw new IllegalArgumentException("negative dash length");
-				}
-			}
-			
-			if (allzero) {
-				throw new IllegalArgumentException("dash lengths all zero");
-			}
-		}
-		this.width	= width;
-		this.cap	= cap;
-		this.join	= join;
-		this.miterlimit = miterlimit;
-		if (dash != null) {
-			this.dash = (float []) dash.clone();
-		}
-		this.dash_phase = dash_phase;
-	}
-	
-	public Object clone() {
-		try {
-			return super.clone();
-		} catch (CloneNotSupportedException e) {
-			e.printStackTrace();
-		}
-		return null;
-	}
-	
-	public Shape createStrokedShape(Shape s) {
-		FillAdapter filler = new FillAdapter();
-		PathStroker stroker = new PathStroker(filler);
-		PathConsumer consumer;
-
-		// Fixed Width Additions, always stroke path inside shape.
-		float fixedWidth = 1;
-		
-		if (PDebug.getProcessingOutput()) {
-			if (PPaintContext.CURRENT_PAINT_CONTEXT != null) fixedWidth = width / (float) PPaintContext.CURRENT_PAINT_CONTEXT.getScale();
-		} else {
-			if (PPickPath.CURRENT_PICK_PATH != null) fixedWidth = width / (float) PPickPath.CURRENT_PICK_PATH.getScale();
-		}
-		
-		Rectangle2D bounds = s.getBounds2D();
-		double scale = 1.0;
-	
-		if (bounds.getWidth() > bounds.getHeight()) {
-			if (bounds.getWidth() != 0) {
-				scale = (bounds.getWidth()-fixedWidth)/bounds.getWidth();
-			}
-		} else {
-			if (bounds.getHeight() != 0) {
-				scale = (bounds.getHeight()-fixedWidth)/bounds.getHeight();
-			}
-		}
-	
-		TEMP_TRANSFORM.setToIdentity();
-		TEMP_TRANSFORM.scaleAboutPoint(scale, bounds.getCenterX(), bounds.getCenterY());
-		stroker.setPenDiameter(fixedWidth);
-		PathIterator pi = s.getPathIterator(TEMP_TRANSFORM);
-		
-		stroker.setPenT4(null);
-		stroker.setCaps(RasterizerCaps[cap]);
-		stroker.setCorners(RasterizerCorners[join], miterlimit);
-		if (dash != null) {
-			PathDasher dasher = new PathDasher(stroker);
-			dasher.setDash(dash, dash_phase);
-			dasher.setDashT4(null);
-			consumer = dasher;
-		} else {
-			consumer = stroker;
-		}
-
-		try {
-			consumer.beginPath();
-			boolean pathClosed = false;
-			float mx = 0.0f;
-			float my = 0.0f;
-			float point[]  = new float[6];
-
-			while (!pi.isDone()) {
-				int type = pi.currentSegment(point);
-				if (pathClosed == true) {
-					pathClosed = false;
-					if (type != PathIterator.SEG_MOVETO) {
-						// Force current point back to last moveto point
-						consumer.beginSubpath(mx, my);
-					}
-				}
-				switch (type) {
-					case PathIterator.SEG_MOVETO:
-						mx = point[0];
-						my = point[1];
-						consumer.beginSubpath(point[0], point[1]);
-						break;
-					case PathIterator.SEG_LINETO:
-						consumer.appendLine(point[0], point[1]);
-						break;
-					case PathIterator.SEG_QUADTO:
-						// Quadratic curves take two points
-						consumer.appendQuadratic(point[0], point[1],
-									 point[2], point[3]);
-						break;
-					case PathIterator.SEG_CUBICTO:
-						// Cubic curves take three points
-						consumer.appendCubic(point[0], point[1],
-								 point[2], point[3],
-								 point[4], point[5]);
-						break;
-					case PathIterator.SEG_CLOSE:
-						consumer.closedSubpath();
-						pathClosed = true;
-						break;
-				}
-				pi.next();
-			}
-
-			consumer.endPath();
-		} catch (PathException e) {
-			throw new InternalError("Unable to Stroke shape ("+
-						e.getMessage()+")");
-		}
-
-		return filler.getShape();
-	}
-	
-	public boolean equals(Object obj) {
-		if (!(obj instanceof PFixedWidthStroke)) {
-			return false;
-		}
-
-		PFixedWidthStroke bs = (PFixedWidthStroke) obj;
-		if (width != bs.width) {
-			return false;
-		}
-
-		if (join != bs.join) {
-			return false;
-		}
-
-		if (cap != bs.cap) {
-			return false;
-		}
-
-		if (miterlimit != bs.miterlimit) {
-			return false;
-		}
-
-		if (dash != null) {
-			if (dash_phase != bs.dash_phase) {
-				return false;
-			}
-
-			if (!java.util.Arrays.equals(dash, bs.dash)) {
-				return false;
-			}
-		} else if (bs.dash != null) {
-			return false;
-		}
-
-		return true;
-	}
-	
-	public float[] getDashArray() {
-		if (dash == null) {
-			return null;
-		}
-
-		return (float[]) dash.clone();
-	}
-	
-	public float getDashPhase() {
-		return dash_phase;
-	}
-	
-	public int getEndCap() {
-		return cap;
-	}
-	
-	public int getLineJoin() {
-		return join;
-	}
-	
-	public float getLineWidth() {
-		return width;
-	}
-	
-	public float getMiterLimit() {
-		return miterlimit;
-	}
-	
-	public int hashCode() {
-		int hash = Float.floatToIntBits(width);
-		hash = hash * 31 + join;
-		hash = hash * 31 + cap;
-		hash = hash * 31 + Float.floatToIntBits(miterlimit);
-		if (dash != null) {
-			hash = hash * 31 + Float.floatToIntBits(dash_phase);
-			for (int i = 0; i < dash.length; i++) {
-				hash = hash * 31 + Float.floatToIntBits(dash[i]);
-			}
-		}
-		return hash;
-	}
-}
+/*
+ * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org
+ * Copyright (c) 1998-2008, University of Maryland
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of conditions
+ * and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
+ * and the following disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
+ * contributors may be used to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package edu.umd.cs.piccolox.util;
+
+import java.awt.BasicStroke;
+import java.awt.Stroke;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * <b>PFixedWidthStroke</b> is the same as {@link BasicStroke} except that
+ * PFixedWidthStroke has a fixed width on the screen so that even when the
+ * canvas view is zooming its width stays the same in canvas coordinates.
+ * <p>
+ * {@link #createStrokedShape(Shape)} checks if the scale has changed since the
+ * last usage and if that's the case calls {@link #newStroke(float)} to get a
+ * new {@link Stroke} instance to delegate to.
+ * <p>
+ * <b>CAUTION!</b> this implementation falls short for large scaling factors -
+ * the effective miterlimit might drop below 1.0 which isn't permitted by
+ * {@link BasicStroke} and therefore limited to a minimal 1.0 by this
+ * implementation. A more sophisticated implementation might use the approach
+ * mentioned at http://code.google.com/p/piccolo2d/issues/detail?id=49
+ * <p>
+ * <b>CAUTION!</b> after extreme scaling this implementation seems to change to
+ * internal state of the base stroke. Try PathExample with extreme zoom in and
+ * zoom back to the original scale. The pickable circles disappear. Strange!
+ * 
+ * @see edu.umd.cs.piccolo.nodes.PPath
+ * @see BasicStroke
+ * @version 1.0
+ * @author Jesse Grosjean
+ */
+public class PFixedWidthStroke extends PSemanticStroke implements Serializable {
+
+    private static final float DEFAULT_MITER_LIMIT = 10.0f;
+
+    private static final BasicStroke DEFAULT_STROKE = new BasicStroke(1.0f, BasicStroke.CAP_SQUARE,
+            BasicStroke.JOIN_MITER, DEFAULT_MITER_LIMIT, null, 0.0f);
+
+    private static final long serialVersionUID = 1L;
+
+    // avoid repeated cloning:
+    private final transient float[] dash;
+
+    // avoid repeated instantiations:
+    private final transient float[] tmpDash;
+
+    /**
+     * Constructs a simple PFixedWidthStroke with the default stroke.
+     */
+    public PFixedWidthStroke() {
+        this(DEFAULT_STROKE);
+    }
+
+    /**
+     * Making this constructor public would break encapsulation. Users don't
+     * need to know that they are dealing with an adapter to an underlying
+     * stroke.
+     * 
+     * @param stroke stroke being used by this PFixedWithStroke
+     */
+    private PFixedWidthStroke(final BasicStroke stroke) {
+        super(stroke);
+        dash = stroke.getDashArray();
+        if (dash == null) {
+            tmpDash = null;
+        }
+        else {
+            tmpDash = new float[dash.length];
+        }
+    }
+
+    /**
+     * Constructs a simple PFixedWidthStroke with the width provided.
+     * 
+     * @param width desired width of the stroke
+     */
+    public PFixedWidthStroke(final float width) {
+        this(width, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, DEFAULT_MITER_LIMIT, null, 0.0f);
+    }
+
+    /**
+     * Constructs a PFixedWidthStroke with the stroke properties provided.
+     * 
+     * @param width width of stroke
+     * @param cap cap to use in stroke
+     * @param join join to use in stroke
+     */
+    public PFixedWidthStroke(final float width, final int cap, final int join) {
+        this(width, cap, join, DEFAULT_MITER_LIMIT, null, 0.0f);
+    }
+
+    /**
+     * Constructs a PFixedWidthStroke with the stroke properties provided.
+     * 
+     * @param width width of stroke
+     * @param cap cap to use in stroke
+     * @param join join to use in stroke
+     * @param miterlimit miter limit of stroke
+     */
+    public PFixedWidthStroke(final float width, final int cap, final int join, final float miterlimit) {
+        this(width, cap, join, miterlimit, null, 0.0f);
+    }
+
+    /**
+     * Constructs a PFixedWidthStroke with the stroke properties provided.
+     * 
+     * @param width width of stroke
+     * @param cap cap to use in stroke
+     * @param join join to use in stroke
+     * @param miterlimit miter limit of stroke
+     * @param dash array of dash lengths
+     * @param dashPhase phase to use when rendering dashes
+     */
+    public PFixedWidthStroke(final float width, final int cap, final int join, final float miterlimit,
+            final float[] dash, final float dashPhase) {
+        this(new BasicStroke(width, cap, join, miterlimit, dash, dashPhase));
+    }
+
+    /**
+     * Throws an exception since PFixedWidthStrokes are not serializable.
+     * 
+     * @return never returns anything
+     */
+    public Object clone() {
+        throw new UnsupportedOperationException("Not implemented.");
+    }
+
+    /**
+     * Returns the array representing the lengths of the dash segments.
+     * Alternate entries in the array represent the user space lengths of the
+     * opaque and transparent segments of the dashes. As the pen moves along the
+     * outline of the Shape to be stroked, the user space distance that the pen
+     * travels is accumulated. The distance value is used to index into the dash
+     * array. The pen is opaque when its current cumulative distance maps to an
+     * even element of the dash array and transparent otherwise.
+     * 
+     * @return the dash array
+     */
+    public float[] getDashArray() {
+        return ((BasicStroke) stroke).getDashArray();
+    }
+
+    /**
+     * Returns the current dash phase. The dash phase is a distance specified in
+     * user coordinates that represents an offset into the dashing pattern. In
+     * other words, the dash phase defines the point in the dashing pattern that
+     * will correspond to the beginning of the stroke.
+     * 
+     * @return the dash phase as a float value.
+     */
+    public float getDashPhase() {
+        return ((BasicStroke) stroke).getDashPhase();
+    }
+
+    /**
+     * Returns the end cap style. 
+     * 
+     * @return the end cap style of this BasicStroke as one of the static int values that define possible end cap styles.
+     */
+    public int getEndCap() {
+        return ((BasicStroke) stroke).getEndCap();
+    }
+
+    /**
+     * Returns the line join style.
+     * 
+     * @return the line join style of the <code>PFixedWidthStroke</code> as one
+     *         of the static <code>int</code> values that define possible line
+     *         join styles.
+     */
+    public int getLineJoin() {
+        return ((BasicStroke) stroke).getLineJoin();
+    }
+
+    /**
+     * Returns the line width. Line width is represented in user space, which is
+     * the default-coordinate space used by Java 2D. See the Graphics2D class
+     * comments for more information on the user space coordinate system.
+     * 
+     * @return the line width of this BasicStroke.
+     */
+    public float getLineWidth() {
+        return ((BasicStroke) stroke).getLineWidth();
+    }
+
+    /**
+     * Returns the miter limit of this node.
+     * 
+     * @return the limit of miter joins of the PFixedWidthStroke
+     */
+    public float getMiterLimit() {
+        return ((BasicStroke) stroke).getMiterLimit();
+    }
+
+    /**
+     * Returns a stroke equivalent to this one, but scaled by the scale
+     * provided.
+     * 
+     * @param activeScale scale to apply to the new stoke
+     * @return scaled stroke
+     */
+    protected Stroke newStroke(final float activeScale) {
+        if (tmpDash != null) {
+            for (int i = dash.length - 1; i >= 0; i--) {
+                tmpDash[i] = dash[i] / activeScale;
+            }
+        }
+        final float ml = getMiterLimit() / activeScale;
+        final float sanitizedMiterLimit;
+        if (ml < 1.0f) {
+            sanitizedMiterLimit = 1f;
+        }
+        else {
+            sanitizedMiterLimit = ml;
+        }
+
+        return new BasicStroke(getLineWidth() / activeScale, getEndCap(), getLineJoin(), sanitizedMiterLimit, tmpDash,
+                getDashPhase() / activeScale);
+    }
+
+    /**
+     * Is it really necessary to implement {@link Serializable}?
+     * 
+     * @throws ObjectStreamException doesn't actually throw this at all, why's
+     *             this here?
+     * @return the resolved stroke
+     */
+    protected Object readResolve() throws ObjectStreamException {
+        return new PFixedWidthStroke((BasicStroke) stroke);
+    }
+}
--- /dev/null
+++ b/extras/edu/umd/cs/piccolox/util/PSemanticStroke.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of conditions
+ * and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
+ * and the following disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
+ * contributors may be used to endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package edu.umd.cs.piccolox.util;
+
+import java.awt.Shape;
+import java.awt.Stroke;
+
+
+
+/**
+ * 
+ * @see  edu.umd.cs.piccolo.nodes.PPath
+ * @see Stroke
+ * @version 1.3
+ * @author Marcus Rohrmoser
+ */
+abstract class PSemanticStroke implements Stroke {
+    protected static final double THRESHOLD = 1e-6;
+
+    private transient float recentScale;
+    private transient Stroke recentStroke;
+    protected final Stroke stroke;
+
+    protected PSemanticStroke(final Stroke stroke) {
+        this.stroke = stroke;
+        recentStroke = stroke;
+        recentScale = 1.0F;
+    }
+
+    /**
+     * Ask {@link #getActiveScale()}, call {@link #newStroke(float)} if
+     * necessary and delegate to {@link Stroke#createStrokedShape(Shape)}.
+     * 
+     * @param s
+     */
+    public Shape createStrokedShape(final Shape s) {
+        final float currentScale = getActiveScale();
+        if (Math.abs(currentScale - recentScale) > THRESHOLD) {
+            recentScale = currentScale;
+            recentStroke = newStroke(recentScale);
+        }
+        return recentStroke.createStrokedShape(s);
+    }
+
+    /**
+     * Returns true if this stroke is equivalent to the object provided.
+     * 
+     * @param obj Object being tested
+     * @return true if object is equivalent
+     */
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final PSemanticStroke other = (PSemanticStroke) obj;
+        if (stroke == null) {
+            if (other.stroke != null) {
+                return false;
+            }
+        }
+        else if (!stroke.equals(other.stroke)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Detect the current scale. Made protected to enable custom
+     * re-implementations.
+     */
+    protected float getActiveScale() {
+        return 1.0f;
+    }
+
+    public int hashCode() {
+        final int prime = 31;
+        int result = prime;
+
+        if (stroke != null) {
+            result += stroke.hashCode();
+        }
+
+        return result;
+    }
+
+    /**
+     * Factory to create a new internal stroke delegate. Made protected to
+     * enable custom re-implementations.
+     */
+    protected abstract Stroke newStroke(final float activeScale);
+
+    public String toString() {
+        return stroke.toString();
+    }
+}
