publicstatic Shape EmptyShapeNonZero = new EmptyShape(WIND_NON_ZERO); publicstatic Shape EmptyShapeEvenOdd = new EmptyShape(WIND_EVEN_ODD);
// Note: We pick a shape that is not anywhere near any of // our test shapes so that the Path2D does not try to collapse // out the connecting segment - an optimization that is too // difficult to account for in the AppendedShape code. publicstatic Shape AppendShape = new Arc2D.Double(1000, 1000, 40, 40,
Math.PI/4, Math.PI,
Arc2D.CHORD);
publicstatic AffineTransform makeAT() {
AffineTransform at = new AffineTransform();
at.scale(0.66, 0.23);
at.rotate(Math.toRadians(35.0));
at.shear(0.78, 1.32); return at;
}
Random random;
public UnitTest(long randomSeed) { this.random = new Random(randomSeed);
TestShapes = new Shape[] {
EmptyShapeNonZero,
EmptyShapeEvenOdd, new Line2D.Double(), new Line2D.Double(rpc(), rpc(), rpc(), rpc()), new Line2D.Double(rnc(), rnc(), rnc(), rnc()), new Rectangle2D.Double(), new Rectangle2D.Double(rpc(), rpc(), -1, -1), new Rectangle2D.Double(rpc(), rpc(), rd(), rd()), new Rectangle2D.Double(rnc(), rnc(), rd(), rd()), new Ellipse2D.Double(), new Ellipse2D.Double(rpc(), rpc(), -1, -1), new Ellipse2D.Double(rpc(), rpc(), rd(), rd()), new Ellipse2D.Double(rnc(), rnc(), rd(), rd()), new Arc2D.Double(Arc2D.OPEN), new Arc2D.Double(Arc2D.CHORD), new Arc2D.Double(Arc2D.PIE), new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.OPEN), new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.CHORD), new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.PIE), new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.OPEN), new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.CHORD), new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.PIE), new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.OPEN), new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.CHORD), new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.PIE), new RoundRectangle2D.Double(), new RoundRectangle2D.Double(rpc(), rpc(), -1, -1, ra(), ra()), new RoundRectangle2D.Double(rpc(), rpc(), rd(), rd(), ra(), ra()), new RoundRectangle2D.Double(rnc(), rnc(), rd(), rd(), ra(), ra()), new QuadCurve2D.Double(), new QuadCurve2D.Double(rpc(), rpc(), rpc(), rpc(), rpc(), rpc()), new QuadCurve2D.Double(rnc(), rnc(), rnc(), rnc(), rnc(), rnc()), new CubicCurve2D.Double(), new CubicCurve2D.Double(rpc(), rpc(), rpc(), rpc(),
rpc(), rpc(), rpc(), rpc()), new CubicCurve2D.Double(rnc(), rnc(), rnc(), rnc(),
rnc(), rnc(), rnc(), rnc()),
makeGeneralPath(WIND_NON_ZERO, 1.0),
makeGeneralPath(WIND_EVEN_ODD, 1.0),
makeGeneralPath(WIND_NON_ZERO, -1.0),
makeGeneralPath(WIND_EVEN_ODD, -1.0),
makeJDK8176501(),
// this shape has a special property: some coefficients to the t^3 term // are *nearly* zero. And analytically they should be zero, but machine // error prevented it. In these cases cubic polynomials should degenerate // into quadratic polynomials, but because the coefficient is not exactly // zero that may not always be handled correctly:
AffineTransform.getRotateInstance(Math.PI / 4).createTransformedShape( new Ellipse2D.Float(0, 0, 100, 100))
};
int types[] = newint[100]; int i = 0;
types[i++] = PathIterator.SEG_MOVETO;
types[i++] = PathIterator.SEG_LINETO;
types[i++] = PathIterator.SEG_QUADTO;
types[i++] = PathIterator.SEG_CUBICTO;
types[i++] = PathIterator.SEG_CLOSE; int shortlen = i; int prevt = types[i-1]; while (i < types.length) { int t; do {
t = (int) (random.nextDouble() * 5);
} while (t == prevt &&
(t == PathIterator.SEG_MOVETO ||
t == PathIterator.SEG_CLOSE));
types[i++] = t;
prevt = t;
}
int numcoords = 0; int numshortcoords = 0; for (i = 0; i < types.length; i++) { if (i == shortlen) {
numshortcoords = numcoords;
}
numcoords += CoordsForType[types[i]];
} double coords[] = newdouble[numcoords]; for (i = 0; i < coords.length; i++) {
coords[i] = rpc();
}
ShortSampleNonZero = new SampleShape(WIND_NON_ZERO,
types, coords,
shortlen, numshortcoords);
ShortSampleEvenOdd = new SampleShape(WIND_EVEN_ODD,
types, coords,
shortlen, numshortcoords);
LongSampleNonZero = new SampleShape(WIND_NON_ZERO,
types, coords,
types.length, numcoords);
LongSampleEvenOdd = new SampleShape(WIND_EVEN_ODD,
types, coords,
types.length, numcoords);
}
// Due to odd issues with the sizes of errors when the values // being manipulated are near zero, we try to avoid values // near zero by ensuring that both the rpc (positive coords) // stay away from zero and also by ensuring that the rpc+rd // (positive coords + dimensions) stay away from zero. We // also ensure that rnc+rd (negative coords + dimension) stay // suitably negative without approaching zero.
// Random positive coordinate (10 -> 110) // rpc + rd gives a total range of (30 -> 170) publicdouble rpc() { return (random.nextDouble() * 100.0) + 10.0;
}
// Random negative coordinate (-200 -> -100) // rnc + rd gives a total range of (-180 -> -40) publicdouble rnc() { return (random.nextDouble() * 100.0) - 200.0;
}
if (tContainsXY != uContainsXY) { thrownew RuntimeException("contains(x,y) "+ "does not match utility");
} if (tContainsXYWH != uContainsXYWH) { thrownew RuntimeException("contains(x,y,w,h) "+ "does not match utility");
} if (tIntersectsXYWH != uIntersectsXYWH) { thrownew RuntimeException("intersects(x,y,w,h) "+ "does not match utility");
}
// Make rect slightly smaller to be more conservative for rContains double srx = rx + 0.1; double sry = ry + 0.1; double srw = rw - 0.2; double srh = rh - 0.2;
Rectangle2D srect = new Rectangle2D.Double(srx, sry, srw, srh); // Make rect slightly larger to be more liberal for rIntersects double lrx = rx - 0.1; double lry = ry - 0.1; double lrw = rw + 0.2; double lrh = rh + 0.2;
Rectangle2D lrect = new Rectangle2D.Double(lrx, lry, lrw, lrh);
if (srect.isEmpty()) { thrownew InternalError("smaller rect too small (empty)");
} if (!lrect.contains(rect)) { thrownew InternalError("test rect not inside larger rect!");
} if (!rect.contains(srect)) { thrownew InternalError("smaller rect not inside test rect!");
}
if (sref instanceof SampleShape ||
sref instanceof QuadCurve2D ||
sref instanceof CubicCurve2D)
{ // REMIND // Some of the source shapes are not proving reliable // enough to do reference verification of the hit // testing results. // Quad/CubicCurve2D have spaghetti test methods that could // very likely contain some bugs. They return a conflicting // answer in maybe 1 out of 20,000 tests. // Area causes a conflicting answer maybe 1 out of // 100 to 1000 runs and it infinite loops maybe 1 // out of 10,000 runs or so. // So, we use some conservative "safe" answers for // these shapes and avoid their hit testing methods.
rContainsSmaller = tContainsRect;
rIntersectsLarger = tIntersectsRect;
rContainsPnt = tContainsPnt;
} else {
rContainsSmaller = sref.contains(srect);
rIntersectsLarger = sref.intersects(lrect);
rContainsPnt = sref.contains(px, py);
}
if (tIntersectsRect) { if (tContainsRect) { if (!tContainsPnt) {
System.out.println("reference shape = "+sref);
System.out.println("pnt = "+pnt);
System.out.println("rect = "+rect);
System.out.println("tbounds = "+stest.getBounds2D()); thrownew RuntimeException("test contains rect, "+ "but not center point");
}
} // Note: (tContainsPnt || tContainsRect) is same as // tContainsPnt because of the test above... if (tContainsPnt) { if (!rIntersectsLarger) {
System.out.println("reference shape = "+sref);
System.out.println("pnt = "+pnt);
System.out.println("rect = "+rect);
System.out.println("lrect = "+lrect);
System.out.println("tbounds = "+stest.getBounds2D());
System.out.println("rbounds = "+sref.getBounds2D()); thrownew RuntimeException("test claims containment, "+ "but no ref intersection");
}
}
} else { if (tContainsRect) { thrownew RuntimeException("test contains rect, "+ "with no intersection");
} if (tContainsPnt) {
System.out.println("reference shape = "+sref);
System.out.println("rect = "+rect); thrownew RuntimeException("test contains point, "+ "with no intersection");
} if (rContainsPnt || rContainsSmaller) {
System.out.println("pnt = "+pnt);
System.out.println("rect = "+rect);
System.out.println("srect = "+lrect); thrownew RuntimeException("test did not intersect, "+ "but ref claims containment");
}
}
}
}
publicvoid test(Creator c) {
testConstructors(c);
testPathConstruction(c);
testAppend(c);
testBounds(c);
testHits(c);
}
publicstaticvoid testAppend(Creator c) { for (int i = 0; i < TestShapes.length; i++) {
Shape sref = TestShapes[i]; if (verbose) System.out.println("append testing "+sref);
PathIterator spi = sref.getPathIterator(null);
Path2D stest = c.makePath(spi.getWindingRule());
stest.append(spi, false);
compare(c, stest, sref, null, 0);
stest.reset();
stest.append(sref, false);
compare(c, stest, sref, null, 0);
stest.reset();
stest.append(sref.getPathIterator(TxComplex), false);
compare(c, stest, sref, TxComplex, 0); // multiple shape append testing... if (sref.getBounds2D().isEmpty()) { // If the first shape is empty, then we really // are not testing multiple appended shapes, // we are just testing appending the AppendShape // to a null path over and over. // Also note that some empty shapes will spit out // a single useless SEG_MOVETO that has no affect // on the outcome, but it makes duplicating the // behavior that Path2D has in that case difficult // when the AppenedShape utility class has to // iterate the exact same segments. So, we will // just ignore all empty shapes here. continue;
}
stest.reset();
stest.append(sref, false);
stest.append(AppendShape, false);
compare(c, stest, new AppendedShape(sref, AppendShape, false), null, 0);
stest.reset();
stest.append(sref, false);
stest.append(AppendShape, true);
compare(c, stest, new AppendedShape(sref, AppendShape, true), null, 0);
stest.reset();
stest.append(sref.getPathIterator(null), false);
stest.append(AppendShape.getPathIterator(null), false);
compare(c, stest, new AppendedShape(sref, AppendShape, false), null, 0);
stest.reset();
stest.append(sref.getPathIterator(null), false);
stest.append(AppendShape.getPathIterator(null), true);
compare(c, stest, new AppendedShape(sref, AppendShape, true), null, 0);
stest.reset();
stest.append(sref.getPathIterator(TxComplex), false);
stest.append(AppendShape.getPathIterator(TxComplex), false);
compare(c, stest, new AppendedShape(sref, AppendShape, false),
TxComplex, 0);
stest.reset();
stest.append(sref.getPathIterator(TxComplex), false);
stest.append(AppendShape.getPathIterator(TxComplex), true);
compare(c, stest, new AppendedShape(sref, AppendShape, true),
TxComplex, 0);
}
}
publicstaticvoid testBounds(Creator c) { for (int i = 0; i < TestShapes.length; i++) {
Shape sref = TestShapes[i]; if (verbose) System.out.println("bounds testing "+sref);
Shape stest = c.makePath(sref);
checkBounds(c.makePath(sref), sref);
testGetBounds2D(stest);
}
testBounds(c, ShortSampleNonZero);
testBounds(c, ShortSampleEvenOdd);
testBounds(c, LongSampleNonZero);
testBounds(c, LongSampleEvenOdd);
}
publicstaticvoid testBounds(Creator c, SampleShape ref) { if (verbose) System.out.println("bounds testing "+ref); if (c.supportsFloatCompose()) {
checkBounds(ref.makeFloatPath(c), ref);
}
checkBounds(ref.makeDoublePath(c), ref);
}
/** *Makesurethe{@linkShape#getBounds2D()}returnsaRectangle2Dthattightlyfitsthe *shapedata.Itshouldn'tcontainlotsofdeadspace(seeJDK8176501),anditshouldn't *leaveoutanyshapepath.Thistestreliesontheaccuracyof *{@linkShape#intersects(double,double,double,double)}
*/ publicstaticvoid testGetBounds2D(Shape shape) { // first: make sure the shape is actually close to the perimeter of shape.getBounds2D(). // this is the crux of JDK 8176501:
Rectangle2D r = shape.getBounds2D();
if (r.getWidth() == 0 || r.getHeight() == 0) { // this can happen for completely empty paths, which are part of our // edge test cases in this class. return;
}
if (verbose) System.out.println("testGetBounds2D "+shape+", "+r);
Rectangle2D topStrip = new Rectangle2D.Double(r.getMinX(), r.getMinY(), r.getWidth(), yminInterior - r.getMinY());
Rectangle2D leftStrip = new Rectangle2D.Double(r.getMinX(), r.getMinY(), xminInterior - r.getMinX(), r.getHeight());
Rectangle2D bottomStrip = new Rectangle2D.Double(r.getMinX(), ymaxInterior, r.getWidth(), r.getMaxY() - ymaxInterior);
Rectangle2D rightStrip = new Rectangle2D.Double(xmaxInterior, r.getMinY(), r.getMaxX() - xmaxInterior, r.getHeight()); if (!shape.intersects(topStrip)) { if (verbose)
System.out.println("topStrip = "+topStrip); thrownew RuntimeException("the shape must intersect the top strip of its bounds");
} if (!shape.intersects(leftStrip)) { if (verbose)
System.out.println("leftStrip = " + leftStrip); thrownew RuntimeException("the shape must intersect the left strip of its bounds");
} if (!shape.intersects(bottomStrip)) { if (verbose)
System.out.println("bottomStrip = " + bottomStrip); thrownew RuntimeException("the shape must intersect the bottom strip of its bounds");
} if (!shape.intersects(rightStrip)) { if (verbose)
System.out.println("rightStrip = " + rightStrip); thrownew RuntimeException("the shape must intersect the right strip of bounds");
}
// Similarly: make sure our shape doesn't exist OUTSIDE of r, either. To my knowledge this has never // been a problem, but if it did happen this would be an even more serious breach of contract than // the former case.
// k is simply meant to mean "a large number, functionally similar to infinity for this test" double k = 10000.0;
leftStrip = new Rectangle2D.Double(xminExterior - k, -k, k, 3 * k);
rightStrip = new Rectangle2D.Double(xmaxExterior, -k, k, 3 * k);
topStrip = new Rectangle2D.Double(-k, yminExterior - k, 3 * k, k);
bottomStrip = new Rectangle2D.Double(-k, ymaxExterior, 3 * k, k); if (shape.intersects(leftStrip)) { if (verbose)
System.out.println("leftStrip = " + leftStrip); thrownew RuntimeException("the shape must not intersect anything to the left of its bounds");
} if (shape.intersects(rightStrip)) { if (verbose)
System.out.println("rightStrip = " + rightStrip); thrownew RuntimeException("the shape must not intersect anything to the right of its bounds");
} if (shape.intersects(topStrip)) { if (verbose)
System.out.println("topStrip = " + topStrip); thrownew RuntimeException("the shape must not intersect anything above its bounds");
} if (shape.intersects(bottomStrip)) { if (verbose)
System.out.println("bottomStrip = " + bottomStrip); thrownew RuntimeException("the shape must not intersect anything below its bounds");
}
}
publicvoid testHits(Creator c) { for (int i = 0; i < TestShapes.length; i++) {
Shape sref = TestShapes[i]; if (verbose) System.out.println("hit testing "+sref);
Shape stest = c.makePath(sref);
checkHits(c.makePath(sref), sref);
}
testHits(c, ShortSampleNonZero);
testHits(c, ShortSampleEvenOdd); // These take too long to construct the Area for reference testing //testHits(c, LongSampleNonZero); //testHits(c, LongSampleEvenOdd);
}
publicvoid testHits(Creator c, SampleShape ref) { if (verbose) System.out.println("hit testing "+ref); if (c.supportsFloatCompose()) {
checkHits(ref.makeFloatPath(c), ref);
}
checkHits(ref.makeDoublePath(c), ref);
}
publicstaticvoid main(String argv[]) { // as specific failures come up we can add them to this array to make sure they're frequently tested: long[] previousBugSeeds = newlong[] {
// these are all failures related to JDK-8176501 4603421469924484958L, 4596019360892069260L, 4604586530476373958L, 4603766396818608126L
};
verbose = (argv.length > 1);
for (long seed : previousBugSeeds) {
test("", seed);
}
int limit = (argv.length > 0) ? 10000 : 1; for (int i = 0; i < limit; i++) { long seed = Double.doubleToLongBits(Math.random());
test("loop #" + (i + 1), seed);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.