1 /*
2  * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * @test
26  * @bug 4172661
27  * @summary Tests all public methods of Path2D classes on all 3 variants
28  *          Path2D.Float, Path2D.Double, and GeneralPath.
29  *          REMIND: Note that the hit testing tests will fail
30  *          occasionally due to precision bugs in the various hit
31  *          testing methods in the geometry classes.
32  *          (Failure rates vary from 1 per 100 runs to 1 per thousands).
33  *          See bug 6396047 to track progress on these failures.
34  */
35 
36 import java.awt.Rectangle;
37 import java.awt.Shape;
38 import java.awt.geom.AffineTransform;
39 import java.awt.geom.Arc2D;
40 import java.awt.geom.Area;
41 import java.awt.geom.CubicCurve2D;
42 import java.awt.geom.Ellipse2D;
43 import java.awt.geom.FlatteningPathIterator;
44 import java.awt.geom.GeneralPath;
45 import java.awt.geom.Line2D;
46 import java.awt.geom.Path2D;
47 import java.awt.geom.PathIterator;
48 import java.awt.geom.Point2D;
49 import java.awt.geom.QuadCurve2D;
50 import java.awt.geom.Rectangle2D;
51 import java.awt.geom.RoundRectangle2D;
52 import java.util.NoSuchElementException;
53 
54 public class UnitTest {
55     public static boolean verbose;
56 
57     public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO;
58     public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD;
59 
60     public static int CoordsForType[] = { 2, 2, 4, 6, 0 };
61 
62     public static AffineTransform TxIdentity = new AffineTransform();
63     public static AffineTransform TxComplex = makeAT();
64 
65     public static Shape TestShapes[];
66     public static SampleShape ShortSampleNonZero;
67     public static SampleShape ShortSampleEvenOdd;
68     public static SampleShape LongSampleNonZero;
69     public static SampleShape LongSampleEvenOdd;
70 
71     public static Shape EmptyShapeNonZero =
72         new EmptyShape(WIND_NON_ZERO);
73     public static Shape EmptyShapeEvenOdd =
74         new EmptyShape(WIND_EVEN_ODD);
75 
76     // Note: We pick a shape that is not anywhere near any of
77     // our test shapes so that the Path2D does not try to collapse
78     // out the connecting segment - an optimization that is too
79     // difficult to account for in the AppendedShape code.
80     public static Shape AppendShape = new Arc2D.Double(1000, 1000, 40, 40,
81                                                        Math.PI/4, Math.PI,
82                                                        Arc2D.CHORD);
83 
makeAT()84     public static AffineTransform makeAT() {
85         AffineTransform at = new AffineTransform();
86         at.scale(0.66, 0.23);
87         at.rotate(Math.toRadians(35.0));
88         at.shear(0.78, 1.32);
89         return at;
90     }
91 
init()92     public static void init() {
93         TestShapes = new Shape[] {
94             EmptyShapeNonZero,
95             EmptyShapeEvenOdd,
96             new Line2D.Double(),
97             new Line2D.Double(rpc(), rpc(), rpc(), rpc()),
98             new Line2D.Double(rnc(), rnc(), rnc(), rnc()),
99             new Rectangle2D.Double(),
100             new Rectangle2D.Double(rpc(), rpc(), -1, -1),
101             new Rectangle2D.Double(rpc(), rpc(), rd(), rd()),
102             new Rectangle2D.Double(rnc(), rnc(), rd(), rd()),
103             new Ellipse2D.Double(),
104             new Ellipse2D.Double(rpc(), rpc(), -1, -1),
105             new Ellipse2D.Double(rpc(), rpc(), rd(), rd()),
106             new Ellipse2D.Double(rnc(), rnc(), rd(), rd()),
107             new Arc2D.Double(Arc2D.OPEN),
108             new Arc2D.Double(Arc2D.CHORD),
109             new Arc2D.Double(Arc2D.PIE),
110             new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.OPEN),
111             new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.CHORD),
112             new Arc2D.Double(rpc(), rpc(), -1, -1, rt(), rt(), Arc2D.PIE),
113             new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.OPEN),
114             new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.CHORD),
115             new Arc2D.Double(rpc(), rpc(), rd(), rd(), rt(), rt(), Arc2D.PIE),
116             new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.OPEN),
117             new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.CHORD),
118             new Arc2D.Double(rnc(), rnc(), rd(), rd(), rt(), rt(), Arc2D.PIE),
119             new RoundRectangle2D.Double(),
120             new RoundRectangle2D.Double(rpc(), rpc(), -1, -1, ra(), ra()),
121             new RoundRectangle2D.Double(rpc(), rpc(), rd(), rd(), ra(), ra()),
122             new RoundRectangle2D.Double(rnc(), rnc(), rd(), rd(), ra(), ra()),
123             new QuadCurve2D.Double(),
124             new QuadCurve2D.Double(rpc(), rpc(), rpc(), rpc(), rpc(), rpc()),
125             new QuadCurve2D.Double(rnc(), rnc(), rnc(), rnc(), rnc(), rnc()),
126             new CubicCurve2D.Double(),
127             new CubicCurve2D.Double(rpc(), rpc(), rpc(), rpc(),
128                                     rpc(), rpc(), rpc(), rpc()),
129             new CubicCurve2D.Double(rnc(), rnc(), rnc(), rnc(),
130                                     rnc(), rnc(), rnc(), rnc()),
131             makeGeneralPath(WIND_NON_ZERO, 1.0),
132             makeGeneralPath(WIND_EVEN_ODD, 1.0),
133             makeGeneralPath(WIND_NON_ZERO, -1.0),
134             makeGeneralPath(WIND_EVEN_ODD, -1.0),
135         };
136 
137         int types[] = new int[100];
138         int i = 0;
139         types[i++] = PathIterator.SEG_MOVETO;
140         types[i++] = PathIterator.SEG_LINETO;
141         types[i++] = PathIterator.SEG_QUADTO;
142         types[i++] = PathIterator.SEG_CUBICTO;
143         types[i++] = PathIterator.SEG_CLOSE;
144         int shortlen = i;
145         int prevt = types[i-1];
146         while (i < types.length) {
147             int t;
148             do {
149                 t = (int) (Math.random() * 5);
150             } while (t == prevt &&
151                      (t == PathIterator.SEG_MOVETO ||
152                       t == PathIterator.SEG_CLOSE));
153             types[i++] = t;
154             prevt = t;
155         }
156 
157         int numcoords = 0;
158         int numshortcoords = 0;
159         for (i = 0; i < types.length; i++) {
160             if (i == shortlen) {
161                 numshortcoords = numcoords;
162             }
163             numcoords += CoordsForType[types[i]];
164         }
165         double coords[] = new double[numcoords];
166         for (i = 0; i < coords.length; i++) {
167             coords[i] = rpc();
168         }
169         ShortSampleNonZero = new SampleShape(WIND_NON_ZERO,
170                                              types, coords,
171                                              shortlen, numshortcoords);
172         ShortSampleEvenOdd = new SampleShape(WIND_EVEN_ODD,
173                                              types, coords,
174                                              shortlen, numshortcoords);
175         LongSampleNonZero = new SampleShape(WIND_NON_ZERO,
176                                             types, coords,
177                                             types.length, numcoords);
178         LongSampleEvenOdd = new SampleShape(WIND_EVEN_ODD,
179                                             types, coords,
180                                             types.length, numcoords);
181     }
182 
makeGeneralPath(int windingrule, double sign)183     public static GeneralPath makeGeneralPath(int windingrule, double sign) {
184         GeneralPath gp = new GeneralPath(windingrule);
185         gp.moveTo((float) (sign * rpc()), (float) (sign * rpc()));
186         gp.lineTo((float) (sign * rpc()), (float) (sign * rpc()));
187         gp.quadTo((float) (sign * rpc()), (float) (sign * rpc()),
188                   (float) (sign * rpc()), (float) (sign * rpc()));
189         gp.curveTo((float) (sign * rpc()), (float) (sign * rpc()),
190                    (float) (sign * rpc()), (float) (sign * rpc()),
191                    (float) (sign * rpc()), (float) (sign * rpc()));
192         gp.closePath();
193         return gp;
194     }
195 
196     // Due to odd issues with the sizes of errors when the values
197     // being manipulated are near zero, we try to avoid values
198     // near zero by ensuring that both the rpc (positive coords)
199     // stay away from zero and also by ensuring that the rpc+rd
200     // (positive coords + dimensions) stay away from zero.  We
201     // also ensure that rnc+rd (negative coords + dimension) stay
202     // suitably negative without approaching zero.
203 
204     // Random positive coordinate (10 -> 110)
205     // rpc + rd gives a total range of (30 -> 170)
rpc()206     public static double rpc() {
207         return (Math.random() * 100.0) + 10.0;
208     }
209 
210     // Random negative coordinate (-200 -> -100)
211     // rnc + rd gives a total range of (-180 -> -40)
rnc()212     public static double rnc() {
213         return (Math.random() * 100.0) - 200.0;
214     }
215 
216     // Random dimension (20 -> 60)
rd()217     public static double rd() {
218         return (Math.random() * 40.0) + 20.0;
219     }
220 
221     // Random arc width/height (0.1 -> 5.1)
ra()222     public static double ra() {
223         return (Math.random() * 5.0) + 0.1;
224     }
225 
226     // Random arc angle (theta) (PI/4 => 5PI/4)
rt()227     public static double rt() {
228         return (Math.random() * Math.PI) + Math.PI/4;
229     }
230 
fltulpdiff(double v1, double v2)231     public static int fltulpdiff(double v1, double v2) {
232         if (v1 == v2) {
233             return 0;
234         }
235         float vf1 = (float) v1;
236         float vf2 = (float) v2;
237         if (vf1 == vf2) {
238             return 0;
239         }
240         float diff = Math.abs(vf1-vf2);
241         //float ulp = Math.ulp((float) ((vf1 + vf2)/2f));
242         float ulp = Math.max(Math.ulp(vf1), Math.ulp(vf2));
243         if (verbose && diff > ulp) {
244             System.out.println("v1 = "+vf1+", ulp = "+Math.ulp(vf1));
245             System.out.println("v2 = "+vf2+", ulp = "+Math.ulp(vf2));
246             System.out.println((diff/ulp)+" ulps");
247         }
248         return (int) (diff/ulp);
249     }
250 
fltulpless(double v1, double v2)251     public static int fltulpless(double v1, double v2) {
252         if (v1 >= v2) {
253             return 0;
254         }
255         float vf1 = (float) v1;
256         float vf2 = (float) v2;
257         if (vf1 >= vf2) {
258             return 0;
259         }
260         float diff = Math.abs(vf1-vf2);
261         //float ulp = Math.ulp((float) ((vf1 + vf2)/2f));
262         float ulp = Math.max(Math.ulp(vf1), Math.ulp(vf2));
263         if (verbose && diff > ulp) {
264             System.out.println("v1 = "+vf1+", ulp = "+Math.ulp(vf1));
265             System.out.println("v2 = "+vf2+", ulp = "+Math.ulp(vf2));
266             System.out.println((diff/ulp)+" ulps");
267         }
268         return (int) (diff/ulp);
269     }
270 
dblulpdiff(double v1, double v2)271     public static int dblulpdiff(double v1, double v2) {
272         if (v1 == v2) {
273             return 0;
274         }
275         double diff = Math.abs(v1-v2);
276         //double ulp = Math.ulp((v1 + v2)/2.0);
277         double ulp = Math.max(Math.ulp(v1), Math.ulp(v2));
278         if (verbose && diff > ulp) {
279             System.out.println("v1 = "+v1+", ulp = "+Math.ulp(v1));
280             System.out.println("v2 = "+v2+", ulp = "+Math.ulp(v2));
281             System.out.println((diff/ulp)+" ulps");
282         }
283         return (int) (diff/ulp);
284     }
285 
286     public static abstract class Creator {
makePath()287         public abstract Path2D makePath();
makePath(int windingrule)288         public abstract Path2D makePath(int windingrule);
makePath(int windingrule, int capacity)289         public abstract Path2D makePath(int windingrule, int capacity);
makePath(Shape s)290         public abstract Path2D makePath(Shape s);
makePath(Shape s, AffineTransform at)291         public abstract Path2D makePath(Shape s, AffineTransform at);
292 
supportsFloatCompose()293         public abstract boolean supportsFloatCompose();
getRecommendedTxMaxUlp()294         public abstract int getRecommendedTxMaxUlp();
295 
compare(PathIterator testpi, PathIterator refpi, AffineTransform at, int maxulp)296         public abstract void compare(PathIterator testpi,
297                                      PathIterator refpi,
298                                      AffineTransform at,
299                                      int maxulp);
300     }
301 
302     public static class FltCreator extends Creator {
makePath()303         public Path2D makePath() {
304             return new Path2D.Float();
305         }
makePath(int windingrule)306         public Path2D makePath(int windingrule) {
307             return new Path2D.Float(windingrule);
308         }
makePath(int windingrule, int capacity)309         public Path2D makePath(int windingrule, int capacity) {
310             return new Path2D.Float(windingrule, capacity);
311         }
makePath(Shape s)312         public Path2D makePath(Shape s) {
313             return new Path2D.Float(s);
314         }
makePath(Shape s, AffineTransform at)315         public Path2D makePath(Shape s, AffineTransform at) {
316             return new Path2D.Float(s, at);
317         }
318 
supportsFloatCompose()319         public boolean supportsFloatCompose() {
320             return true;
321         }
getRecommendedTxMaxUlp()322         public int getRecommendedTxMaxUlp() {
323             return 5;
324         }
325 
compare(PathIterator testpi, PathIterator refpi, AffineTransform at, int maxulp)326         public void compare(PathIterator testpi,
327                             PathIterator refpi,
328                             AffineTransform at,
329                             int maxulp)
330         {
331             if (testpi.getWindingRule() != refpi.getWindingRule()) {
332                 throw new RuntimeException("wrong winding rule");
333             }
334             float testcoords[] = new float[6];
335             float refcoords[] = new float[6];
336             while (!testpi.isDone()) {
337                 if (refpi.isDone()) {
338                     throw new RuntimeException("too many segments");
339                 }
340                 int testtype = testpi.currentSegment(testcoords);
341                 int reftype = refpi.currentSegment(refcoords);
342                 if (testtype != reftype) {
343                     throw new RuntimeException("different segment types");
344                 }
345                 if (at != null) {
346                     at.transform(refcoords, 0, refcoords, 0,
347                                  CoordsForType[reftype]/2);
348                 }
349                 for (int i = 0; i < CoordsForType[testtype]; i++) {
350                     int ulps = fltulpdiff(testcoords[i], refcoords[i]);
351                     if (ulps > maxulp) {
352                         throw new RuntimeException("coords are different: "+
353                                                    testcoords[i]+" != "+
354                                                    refcoords[i]+
355                                                    " ("+ulps+" ulps)");
356                     }
357                 }
358                 testpi.next();
359                 refpi.next();
360             }
361             if (!refpi.isDone()) {
362                 throw new RuntimeException("not enough segments");
363             }
364         }
365     }
366 
367     public static class DblCreator extends Creator {
makePath()368         public Path2D makePath() {
369             return new Path2D.Double();
370         }
makePath(int windingrule)371         public Path2D makePath(int windingrule) {
372             return new Path2D.Double(windingrule);
373         }
makePath(int windingrule, int capacity)374         public Path2D makePath(int windingrule, int capacity) {
375             return new Path2D.Double(windingrule, capacity);
376         }
makePath(Shape s)377         public Path2D makePath(Shape s) {
378             return new Path2D.Double(s);
379         }
makePath(Shape s, AffineTransform at)380         public Path2D makePath(Shape s, AffineTransform at) {
381             return new Path2D.Double(s, at);
382         }
383 
supportsFloatCompose()384         public boolean supportsFloatCompose() {
385             return false;
386         }
getRecommendedTxMaxUlp()387         public int getRecommendedTxMaxUlp() {
388             return 3;
389         }
390 
compare(PathIterator testpi, PathIterator refpi, AffineTransform at, int maxulp)391         public void compare(PathIterator testpi,
392                             PathIterator refpi,
393                             AffineTransform at,
394                             int maxulp)
395         {
396             if (testpi.getWindingRule() != refpi.getWindingRule()) {
397                 throw new RuntimeException("wrong winding rule");
398             }
399             double testcoords[] = new double[6];
400             double refcoords[] = new double[6];
401             while (!testpi.isDone()) {
402                 if (refpi.isDone()) {
403                     throw new RuntimeException("too many segments");
404                 }
405                 int testtype = testpi.currentSegment(testcoords);
406                 int reftype = refpi.currentSegment(refcoords);
407                 if (testtype != reftype) {
408                     throw new RuntimeException("different segment types");
409                 }
410                 if (at != null) {
411                     at.transform(refcoords, 0, refcoords, 0,
412                                  CoordsForType[reftype]/2);
413                 }
414                 for (int i = 0; i < CoordsForType[testtype]; i++) {
415                     int ulps = dblulpdiff(testcoords[i], refcoords[i]);
416                     if (ulps > maxulp) {
417                         throw new RuntimeException("coords are different: "+
418                                                    testcoords[i]+" != "+
419                                                    refcoords[i]+
420                                                    " ("+ulps+" ulps)");
421                     }
422                 }
423                 testpi.next();
424                 refpi.next();
425             }
426             if (!refpi.isDone()) {
427                 throw new RuntimeException("not enough segments");
428             }
429         }
430 
431     }
432 
433     public static class GPCreator extends FltCreator {
makePath()434         public Path2D makePath() {
435             return new GeneralPath();
436         }
makePath(int windingrule)437         public Path2D makePath(int windingrule) {
438             return new GeneralPath(windingrule);
439         }
makePath(int windingrule, int capacity)440         public Path2D makePath(int windingrule, int capacity) {
441             return new GeneralPath(windingrule, capacity);
442         }
makePath(Shape s)443         public Path2D makePath(Shape s) {
444             return new GeneralPath(s);
445         }
makePath(Shape s, AffineTransform at)446         public Path2D makePath(Shape s, AffineTransform at) {
447             GeneralPath gp = new GeneralPath();
448             PathIterator pi = s.getPathIterator(at);
449             gp.setWindingRule(pi.getWindingRule());
450             gp.append(pi, false);
451             return gp;
452         }
453 
supportsFloatCompose()454         public boolean supportsFloatCompose() {
455             return true;
456         }
457     }
458 
459     public static class EmptyShape implements Shape {
460         private int windingrule;
461 
EmptyShape(int windingrule)462         public EmptyShape(int windingrule) {
463             this.windingrule = windingrule;
464         }
465 
getBounds()466         public Rectangle getBounds() {
467             return new Rectangle();
468         }
getBounds2D()469         public Rectangle2D getBounds2D() {
470             return new Rectangle();
471         }
contains(double x, double y)472         public boolean contains(double x, double y) {
473             return false;
474         }
contains(Point2D p)475         public boolean contains(Point2D p) {
476             return false;
477         }
intersects(double x, double y, double w, double h)478         public boolean intersects(double x, double y, double w, double h) {
479             return false;
480         }
intersects(Rectangle2D r)481         public boolean intersects(Rectangle2D r) {
482             return false;
483         }
contains(double x, double y, double w, double h)484         public boolean contains(double x, double y, double w, double h) {
485             return false;
486         }
contains(Rectangle2D r)487         public boolean contains(Rectangle2D r) {
488             return false;
489         }
getPathIterator(AffineTransform at)490         public PathIterator getPathIterator(AffineTransform at) {
491             return new PathIterator() {
492                 public int getWindingRule() {
493                     return windingrule;
494                 }
495                 public boolean isDone() {
496                     return true;
497                 }
498                 public void next() {}
499                 public int currentSegment(float[] coords) {
500                     throw new NoSuchElementException();
501                 }
502                 public int currentSegment(double[] coords) {
503                     throw new NoSuchElementException();
504                 }
505             };
506         }
getPathIterator(AffineTransform at, double flatness)507         public PathIterator getPathIterator(AffineTransform at,
508                                             double flatness)
509         {
510             return getPathIterator(at);
511         }
512     }
513 
514     public static class SampleShape implements Shape {
515         int windingrule;
516         int theTypes[];
517         double theCoords[];
518         int numTypes;
519         int numCoords;
520 
521         public SampleShape(int windingrule,
522                            int types[], double coords[],
523                            int numtypes, int numcoords)
524         {
525             this.windingrule = windingrule;
526             this.theTypes = types;
527             this.theCoords = coords;
528             this.numTypes = numtypes;
529             this.numCoords = numcoords;
530         }
531 
532         private Shape testshape;
533 
534         public Shape getTestShape() {
535             if (testshape == null) {
536                 testshape = new Area(this);
537             }
538             return testshape;
539         }
540 
541         private Rectangle2D cachedBounds;
542         public Rectangle2D getCachedBounds2D() {
543             if (cachedBounds == null) {
544                 double xmin, ymin, xmax, ymax;
545                 int ci = 0;
546                 xmin = xmax = theCoords[ci++];
547                 ymin = ymax = theCoords[ci++];
548                 while (ci < numCoords) {
549                     double c = theCoords[ci++];
550                     if (xmin > c) xmin = c;
551                     if (xmax < c) xmax = c;
552                     c = theCoords[ci++];
553                     if (ymin > c) ymin = c;
554                     if (ymax < c) ymax = c;
555                 }
556                 cachedBounds = new Rectangle2D.Double(xmin, ymin,
557                                                       xmax - xmin,
558                                                       ymax - ymin);
559             }
560             return cachedBounds;
561         }
562 
563         public Rectangle getBounds() {
564             return getCachedBounds2D().getBounds();
565         }
566         public Rectangle2D getBounds2D() {
567             return getCachedBounds2D().getBounds2D();
568         }
569         public boolean contains(double x, double y) {
570             return getTestShape().contains(x, y);
571         }
572         public boolean contains(Point2D p) {
573             return getTestShape().contains(p);
574         }
575         public boolean intersects(double x, double y, double w, double h) {
576             return getTestShape().intersects(x, y, w, h);
577         }
578         public boolean intersects(Rectangle2D r) {
579             return getTestShape().intersects(r);
580         }
581         public boolean contains(double x, double y, double w, double h) {
582             return getTestShape().contains(x, y, w, h);
583         }
584         public boolean contains(Rectangle2D r) {
585             return getTestShape().contains(r);
586         }
587         public PathIterator getPathIterator(final AffineTransform at) {
588             return new PathIterator() {
589                 int tindex;
590                 int cindex;
591                 public int getWindingRule() {
592                     return windingrule;
593                 }
594                 public boolean isDone() {
595                     return (tindex >= numTypes);
596                 }
597                 public void next() {
598                     cindex += CoordsForType[theTypes[tindex]];
599                     tindex++;
600                 }
601                 public int currentSegment(float[] coords) {
602                     int t = theTypes[tindex];
603                     int n = CoordsForType[t];
604                     if (n > 0) {
605                         // Cast to float first, then transform
606                         // to match accuracy of float paths
607                         for (int i = 0; i < n; i++) {
608                             coords[i] = (float) theCoords[cindex+i];
609                         }
610                         if (at != null) {
611                             at.transform(coords, 0, coords, 0, n/2);
612                         }
613                     }
614                     return t;
615                 }
616                 public int currentSegment(double[] coords) {
617                     int t = theTypes[tindex];
618                     int n = CoordsForType[t];
619                     if (n > 0) {
620                         if (at == null) {
621                             System.arraycopy(theCoords, cindex,
622                                              coords, 0, n);
623                         } else {
624                             at.transform(theCoords, cindex,
625                                          coords, 0, n/2);
626                         }
627                     }
628                     return t;
629                 }
630             };
631         }
632         public PathIterator getPathIterator(AffineTransform at,
633                                             double flatness)
634         {
635             return new FlatteningPathIterator(getPathIterator(at), flatness);
636         }
637 
638         public String toString() {
639             Rectangle2D r2d = getBounds2D();
640             double xmin = r2d.getMinX();
641             double ymin = r2d.getMinY();
642             double xmax = r2d.getMaxX();
643             double ymax = r2d.getMaxY();
644             return ("SampleShape["+
645                     (windingrule == WIND_NON_ZERO
646                      ? "NonZero"
647                      : "EvenOdd")+
648                     ", nsegments = "+numTypes+
649                     ", ncoords = "+numCoords+
650                     ", bounds["+(r2d.getMinX()+", "+r2d.getMinY()+", "+
651                                  r2d.getMaxX()+", "+r2d.getMaxY())+"]"+
652                     "]");
653         }
654 
655         public Path2D makeFloatPath(Creator c) {
656             Path2D.Float p2df = (Path2D.Float) c.makePath(windingrule);
657             int ci = 0;
658             for (int i = 0; i < numTypes; i++) {
659                 int t = theTypes[i];
660                 switch (t) {
661                 case PathIterator.SEG_MOVETO:
662                     p2df.moveTo((float) theCoords[ci++],
663                                 (float) theCoords[ci++]);
664                     break;
665                 case PathIterator.SEG_LINETO:
666                     p2df.lineTo((float) theCoords[ci++],
667                                 (float) theCoords[ci++]);
668                     break;
669                 case PathIterator.SEG_QUADTO:
670                     p2df.quadTo((float) theCoords[ci++],
671                                 (float) theCoords[ci++],
672                                 (float) theCoords[ci++],
673                                 (float) theCoords[ci++]);
674                     break;
675                 case PathIterator.SEG_CUBICTO:
676                     p2df.curveTo((float) theCoords[ci++],
677                                  (float) theCoords[ci++],
678                                  (float) theCoords[ci++],
679                                  (float) theCoords[ci++],
680                                  (float) theCoords[ci++],
681                                  (float) theCoords[ci++]);
682                     break;
683                 case PathIterator.SEG_CLOSE:
684                     p2df.closePath();
685                     break;
686                 default:
687                     throw new InternalError("unrecognized path type: "+t);
688                 }
689                 if (t != PathIterator.SEG_CLOSE) {
690                     Point2D curpnt = p2df.getCurrentPoint();
691                     if (((float) curpnt.getX()) != ((float) theCoords[ci-2]) ||
692                         ((float) curpnt.getY()) != ((float) theCoords[ci-1]))
693                     {
694                         throw new RuntimeException("currentpoint failed");
695                     }
696                 }
697             }
698             if (ci != numCoords) {
699                 throw new InternalError("numcoords did not match");
700             }
701             return p2df;
702         }
703 
704         public Path2D makeDoublePath(Creator c) {
705             Path2D p2d = c.makePath(windingrule);
706             int ci = 0;
707             for (int i = 0; i < numTypes; i++) {
708                 int t = theTypes[i];
709                 switch (t) {
710                 case PathIterator.SEG_MOVETO:
711                     p2d.moveTo(theCoords[ci++], theCoords[ci++]);
712                     break;
713                 case PathIterator.SEG_LINETO:
714                     p2d.lineTo(theCoords[ci++], theCoords[ci++]);
715                     break;
716                 case PathIterator.SEG_QUADTO:
717                     p2d.quadTo(theCoords[ci++], theCoords[ci++],
718                                theCoords[ci++], theCoords[ci++]);
719                     break;
720                 case PathIterator.SEG_CUBICTO:
721                     p2d.curveTo(theCoords[ci++], theCoords[ci++],
722                                 theCoords[ci++], theCoords[ci++],
723                                 theCoords[ci++], theCoords[ci++]);
724                     break;
725                 case PathIterator.SEG_CLOSE:
726                     p2d.closePath();
727                     break;
728                 default:
729                     throw new InternalError("unrecognized path type: "+t);
730                 }
731                 if (t != PathIterator.SEG_CLOSE) {
732                     Point2D curpnt = p2d.getCurrentPoint();
733                     if (((float) curpnt.getX()) != ((float) theCoords[ci-2]) ||
734                         ((float) curpnt.getY()) != ((float) theCoords[ci-1]))
735                     {
736                         throw new RuntimeException("currentpoint failed");
737                     }
738                 }
739             }
740             if (ci != numCoords) {
741                 throw new InternalError("numcoords did not match");
742             }
743             return p2d;
744         }
745     }
746 
747     public static class AppendedShape implements Shape {
748         Shape s1;
749         Shape s2;
750         boolean connect;
751 
752         public AppendedShape(Shape s1, Shape s2, boolean connect) {
753             this.s1 = s1;
754             this.s2 = s2;
755             this.connect = connect;
756         }
757 
758         public Rectangle getBounds() {
759             return getBounds2D().getBounds();
760         }
761 
762         public Rectangle2D getBounds2D() {
763             return s1.getBounds2D().createUnion(s2.getBounds2D());
764         }
765 
766         private Shape testshape;
767         private Shape getTestShape() {
768             if (testshape == null) {
769                 testshape = new GeneralPath(this);
770             }
771             return testshape;
772         }
773 
774         public boolean contains(double x, double y) {
775             return getTestShape().contains(x, y);
776         }
777 
778         public boolean contains(Point2D p) {
779             return getTestShape().contains(p);
780         }
781 
782         public boolean intersects(double x, double y, double w, double h) {
783             return getTestShape().intersects(x, y, w, h);
784         }
785 
786         public boolean intersects(Rectangle2D r) {
787             return getTestShape().intersects(r);
788         }
789 
790         public boolean contains(double x, double y, double w, double h) {
791             return getTestShape().contains(x, y, w, h);
792         }
793 
794         public boolean contains(Rectangle2D r) {
795             return getTestShape().contains(r);
796         }
797 
798         public PathIterator getPathIterator(final AffineTransform at) {
799             return new AppendingPathIterator(s1, s2, connect, at);
800         }
801 
802         public PathIterator getPathIterator(AffineTransform at,
803                                             double flatness)
804         {
805             return new FlatteningPathIterator(getPathIterator(at), flatness);
806         }
807 
808         public static class AppendingPathIterator implements PathIterator {
809             AffineTransform at;
810             PathIterator pi;
811             Shape swaiting;
812             int windingrule;
813             boolean connectrequested;
814             boolean canconnect;
815             boolean converttoline;
816 
817             public AppendingPathIterator(Shape s1, Shape s2,
818                                          boolean connect,
819                                          AffineTransform at)
820             {
821                 this.at = at;
822                 this.pi = s1.getPathIterator(at);
823                 this.swaiting = s2;
824                 this.windingrule = pi.getWindingRule();
825                 this.connectrequested = connect;
826 
827                 if (pi.isDone()) {
828                     chain();
829                 }
830             }
831 
832             public void chain() {
833                 if (swaiting != null) {
834                     pi = swaiting.getPathIterator(at);
835                     swaiting = null;
836                     converttoline = (connectrequested && canconnect);
837                 }
838             }
839 
840             public int getWindingRule() {
841                 return windingrule;
842             }
843 
844             public boolean isDone() {
845                 return (pi.isDone());
846             }
847 
848             public void next() {
849                 converttoline = false;
850                 pi.next();
851                 if (pi.isDone()) {
852                     chain();
853                 }
854                 canconnect = true;
855             }
856 
857             public int currentSegment(float[] coords) {
858                 int type = pi.currentSegment(coords);
859                 if (converttoline) {
860                     type = SEG_LINETO;
861                 }
862                 return type;
863             }
864 
865             public int currentSegment(double[] coords) {
866                 int type = pi.currentSegment(coords);
867                 if (converttoline) {
868                     type = SEG_LINETO;
869                 }
870                 return type;
871             }
872         }
873     }
874 
875     public static void checkEmpty(Path2D p2d, int windingrule) {
876         checkEmpty2(p2d, windingrule);
877         p2d.setWindingRule(PathIterator.WIND_NON_ZERO);
878         checkEmpty2(p2d, PathIterator.WIND_NON_ZERO);
879         p2d.setWindingRule(PathIterator.WIND_EVEN_ODD);
880         checkEmpty2(p2d, PathIterator.WIND_EVEN_ODD);
881     }
882 
883     public static void checkEmpty2(Path2D p2d, int windingrule) {
884         if (p2d.getWindingRule() != windingrule) {
885             throw new RuntimeException("wrong winding rule in Path2D");
886         }
887         PathIterator pi = p2d.getPathIterator(null);
888         if (pi.getWindingRule() != windingrule) {
889             throw new RuntimeException("wrong winding rule in iterator");
890         }
891         if (!pi.isDone()) {
892             throw new RuntimeException("path not empty");
893         }
894     }
895 
896     public static void compare(Creator c, Path2D p2d, Shape ref, int maxulp) {
897         compare(c, p2d, (Shape) p2d.clone(), null, 0);
898         compare(c, p2d, ref, null, 0);
899         compare(c, p2d, ref, TxIdentity, 0);
900         p2d.transform(TxIdentity);
901         compare(c, p2d, ref, null, 0);
902         compare(c, p2d, ref, TxIdentity, 0);
903         Shape s2 = p2d.createTransformedShape(TxIdentity);
904         compare(c, s2, ref, null, 0);
905         compare(c, s2, ref, TxIdentity, 0);
906         s2 = p2d.createTransformedShape(TxComplex);
907         compare(c, s2, ref, TxComplex, maxulp);
908         p2d.transform(TxComplex);
909         compare(c, p2d, (Shape) p2d.clone(), null, 0);
910         compare(c, p2d, ref, TxComplex, maxulp);
911     }
912 
913     public static void compare(Creator c,
914                                Shape p2d, Shape s,
915                                AffineTransform at, int maxulp)
916     {
917         c.compare(p2d.getPathIterator(null), s.getPathIterator(at),
918                   null, maxulp);
919         c.compare(p2d.getPathIterator(null), s.getPathIterator(null),
920                   at, maxulp);
921     }
922 
923     public static void checkBounds(Shape stest, Shape sref) {
924         checkBounds(stest.getBounds2D(), sref.getBounds2D(),
925                     "2D bounds too small");
926         /*
927         checkBounds(stest.getBounds(), sref.getBounds(),
928                     "int bounds too small");
929         */
930         checkBounds(stest.getBounds(), stest.getBounds2D(),
931                     "int bounds too small for 2D bounds");
932     }
933 
934     public static void checkBounds(Rectangle2D tBounds,
935                                    Rectangle2D rBounds,
936                                    String faildesc)
937     {
938         if (rBounds.isEmpty()) {
939             if (!tBounds.isEmpty()) {
940                 throw new RuntimeException("bounds not empty");
941             }
942             return;
943         } else if (tBounds.isEmpty()) {
944             throw new RuntimeException("bounds empty");
945         }
946         double rxmin = rBounds.getMinX();
947         double rymin = rBounds.getMinY();
948         double rxmax = rBounds.getMaxX();
949         double rymax = rBounds.getMaxY();
950         double txmin = tBounds.getMinX();
951         double tymin = tBounds.getMinY();
952         double txmax = tBounds.getMaxX();
953         double tymax = tBounds.getMaxY();
954         if (txmin > rxmin || tymin > rymin ||
955             txmax < rxmax || tymax < rymax)
956         {
957             if (verbose) System.out.println("test bounds = "+tBounds);
958             if (verbose) System.out.println("ref bounds = "+rBounds);
959             // Allow fudge room of a couple of single precision ulps
960             double ltxmin = txmin - 5 * Math.max(Math.ulp((float) rxmin),
961                                                  Math.ulp((float) txmin));
962             double ltymin = tymin - 5 * Math.max(Math.ulp((float) rymin),
963                                                  Math.ulp((float) tymin));
964             double ltxmax = txmax + 5 * Math.max(Math.ulp((float) rxmax),
965                                                  Math.ulp((float) txmax));
966             double ltymax = tymax + 5 * Math.max(Math.ulp((float) rymax),
967                                                  Math.ulp((float) tymax));
968             if (ltxmin > rxmin || ltymin > rymin ||
969                 ltxmax < rxmax || ltymax < rymax)
970             {
971                 if (!verbose) System.out.println("test bounds = "+tBounds);
972                 if (!verbose) System.out.println("ref bounds = "+rBounds);
973                 System.out.println("xmin: "+
974                                    txmin+" + "+fltulpless(txmin, rxmin)+" = "+
975                                    rxmin+" + "+fltulpless(rxmin, txmin));
976                 System.out.println("ymin: "+
977                                    tymin+" + "+fltulpless(tymin, rymin)+" = "+
978                                    rymin+" + "+fltulpless(rymin, tymin));
979                 System.out.println("xmax: "+
980                                    txmax+" + "+fltulpless(txmax, rxmax)+" = "+
981                                    rxmax+" + "+fltulpless(rxmax, txmax));
982                 System.out.println("ymax: "+
983                                    tymax+" + "+fltulpless(tymax, rymax)+" = "+
984                                    rymax+" + "+fltulpless(rymax, tymax));
985                 System.out.println("flt tbounds = ["+
986                                    ((float) txmin)+", "+((float) tymin)+", "+
987                                    ((float) txmax)+", "+((float) tymax)+"]");
988                 System.out.println("flt rbounds = ["+
989                                    ((float) rxmin)+", "+((float) rymin)+", "+
990                                    ((float) rxmax)+", "+((float) rymax)+"]");
991                 System.out.println("xmin ulp = "+fltulpless(rxmin, txmin));
992                 System.out.println("ymin ulp = "+fltulpless(rymin, tymin));
993                 System.out.println("xmax ulp = "+fltulpless(txmax, rxmax));
994                 System.out.println("ymax ulp = "+fltulpless(tymax, rymax));
995                 throw new RuntimeException(faildesc);
996             }
997         }
998     }
999 
1000     public static void checkHits(Shape stest, Shape sref) {
1001         for (int i = 0; i < 10; i++) {
1002             double px = Math.random() * 500 - 250;
1003             double py = Math.random() * 500 - 250;
1004             Point2D pnt = new Point2D.Double(px, py);
1005 
1006             double rw = Math.random()*10+0.4;
1007             double rh = Math.random()*10+0.4;
1008             double rx = px - rw/2;
1009             double ry = py - rh/2;
1010             Rectangle2D rect = new Rectangle2D.Double(rx, ry, rw, rh);
1011             Rectangle2D empty = new Rectangle2D.Double(rx, ry, 0, 0);
1012 
1013             if (!rect.contains(pnt)) {
1014                 throw new InternalError("test point not inside test rect!");
1015             }
1016 
1017             if (stest.contains(rx, ry, 0, 0)) {
1018                 throw new RuntimeException("contains 0x0 rect");
1019             }
1020             if (stest.contains(empty)) {
1021                 throw new RuntimeException("contains empty rect");
1022             }
1023             if (stest.intersects(rx, ry, 0, 0)) {
1024                 throw new RuntimeException("intersects 0x0 rect");
1025             }
1026             if (stest.intersects(empty)) {
1027                 throw new RuntimeException("intersects empty rect");
1028             }
1029 
1030             boolean tContainsXY = stest.contains(px, py);
1031             boolean tContainsPnt = stest.contains(pnt);
1032             boolean tContainsXYWH = stest.contains(rx, ry, rw, rh);
1033             boolean tContainsRect = stest.contains(rect);
1034             boolean tIntersectsXYWH = stest.intersects(rx, ry, rw, rh);
1035             boolean tIntersectsRect = stest.intersects(rect);
1036 
1037             if (tContainsXY != tContainsPnt) {
1038                 throw new RuntimeException("contains(x,y) != "+
1039                                            "contains(pnt)");
1040             }
1041             if (tContainsXYWH != tContainsRect) {
1042                 throw new RuntimeException("contains(x,y,w,h) != "+
1043                                            "contains(rect)");
1044             }
1045             if (tIntersectsXYWH != tIntersectsRect) {
1046                 throw new RuntimeException("intersects(x,y,w,h) != "+
1047                                            "intersects(rect)");
1048             }
1049 
1050             boolean uContainsXY =
1051                 Path2D.contains(stest.getPathIterator(null), px, py);
1052             boolean uContainsXYWH =
1053                 Path2D.contains(stest.getPathIterator(null), rx, ry, rw, rh);
1054             boolean uIntersectsXYWH =
1055                 Path2D.intersects(stest.getPathIterator(null), rx, ry, rw, rh);
1056 
1057             if (tContainsXY != uContainsXY) {
1058                 throw new RuntimeException("contains(x,y) "+
1059                                            "does not match utility");
1060             }
1061             if (tContainsXYWH != uContainsXYWH) {
1062                 throw new RuntimeException("contains(x,y,w,h) "+
1063                                            "does not match utility");
1064             }
1065             if (tIntersectsXYWH != uIntersectsXYWH) {
1066                 throw new RuntimeException("intersects(x,y,w,h) "+
1067                                            "does not match utility");
1068             }
1069 
1070             // Make rect slightly smaller to be more conservative for rContains
1071             double srx = rx + 0.1;
1072             double sry = ry + 0.1;
1073             double srw = rw - 0.2;
1074             double srh = rh - 0.2;
1075             Rectangle2D srect = new Rectangle2D.Double(srx, sry, srw, srh);
1076             // Make rect slightly larger to be more liberal for rIntersects
1077             double lrx = rx - 0.1;
1078             double lry = ry - 0.1;
1079             double lrw = rw + 0.2;
1080             double lrh = rh + 0.2;
1081             Rectangle2D lrect = new Rectangle2D.Double(lrx, lry, lrw, lrh);
1082 
1083             if (srect.isEmpty()) {
1084                 throw new InternalError("smaller rect too small (empty)");
1085             }
1086             if (!lrect.contains(rect)) {
1087                 throw new InternalError("test rect not inside larger rect!");
1088             }
1089             if (!rect.contains(srect)) {
1090                 throw new InternalError("smaller rect not inside test rect!");
1091             }
1092 
1093             boolean rContainsSmaller;
1094             boolean rIntersectsLarger;
1095             boolean rContainsPnt;
1096 
1097             if (sref instanceof SampleShape ||
1098                 sref instanceof QuadCurve2D ||
1099                 sref instanceof CubicCurve2D)
1100             {
1101                 // REMIND
1102                 // Some of the source shapes are not proving reliable
1103                 // enough to do reference verification of the hit
1104                 // testing results.
1105                 // Quad/CubicCurve2D have spaghetti test methods that could
1106                 // very likely contain some bugs.  They return a conflicting
1107                 // answer in maybe 1 out of 20,000 tests.
1108                 // Area causes a conflicting answer maybe 1 out of
1109                 // 100 to 1000 runs and it infinite loops maybe 1
1110                 // out of 10,000 runs or so.
1111                 // So, we use some conservative "safe" answers for
1112                 // these shapes and avoid their hit testing methods.
1113                 rContainsSmaller = tContainsRect;
1114                 rIntersectsLarger = tIntersectsRect;
1115                 rContainsPnt = tContainsPnt;
1116             } else {
1117                 rContainsSmaller = sref.contains(srect);
1118                 rIntersectsLarger = sref.intersects(lrect);
1119                 rContainsPnt = sref.contains(px, py);
1120             }
1121 
1122             if (tIntersectsRect) {
1123                 if (tContainsRect) {
1124                     if (!tContainsPnt) {
1125                         System.out.println("reference shape = "+sref);
1126                         System.out.println("pnt = "+pnt);
1127                         System.out.println("rect = "+rect);
1128                         System.out.println("tbounds = "+stest.getBounds2D());
1129                         throw new RuntimeException("test contains rect, "+
1130                                                    "but not center point");
1131                     }
1132                 }
1133                 // Note: (tContainsPnt || tContainsRect) is same as
1134                 // tContainsPnt because of the test above...
1135                 if (tContainsPnt) {
1136                     if (!rIntersectsLarger) {
1137                         System.out.println("reference shape = "+sref);
1138                         System.out.println("pnt = "+pnt);
1139                         System.out.println("rect = "+rect);
1140                         System.out.println("lrect = "+lrect);
1141                         System.out.println("tbounds = "+stest.getBounds2D());
1142                         System.out.println("rbounds = "+sref.getBounds2D());
1143                         throw new RuntimeException("test claims containment, "+
1144                                                    "but no ref intersection");
1145                     }
1146                 }
1147             } else {
1148                 if (tContainsRect) {
1149                     throw new RuntimeException("test contains rect, "+
1150                                                "with no intersection");
1151                 }
1152                 if (tContainsPnt) {
1153                     System.out.println("reference shape = "+sref);
1154                     System.out.println("rect = "+rect);
1155                     throw new RuntimeException("test contains point, "+
1156                                                "with no intersection");
1157                 }
1158                 if (rContainsPnt || rContainsSmaller) {
1159                     System.out.println("pnt = "+pnt);
1160                     System.out.println("rect = "+rect);
1161                     System.out.println("srect = "+lrect);
1162                     throw new RuntimeException("test did not intersect, "+
1163                                                "but ref claims containment");
1164                 }
1165             }
1166         }
1167     }
1168 
1169     public static void test(Creator c) {
1170         testConstructors(c);
1171         testPathConstruction(c);
1172         testAppend(c);
1173         testBounds(c);
1174         testHits(c);
1175     }
1176 
1177     public static void testConstructors(Creator c) {
1178         checkEmpty(c.makePath(), WIND_NON_ZERO);
1179         checkEmpty(c.makePath(WIND_NON_ZERO), WIND_NON_ZERO);
1180         checkEmpty(c.makePath(WIND_EVEN_ODD), WIND_EVEN_ODD);
1181         checkEmpty(c.makePath(EmptyShapeNonZero), WIND_NON_ZERO);
1182         checkEmpty(c.makePath(EmptyShapeNonZero, null), WIND_NON_ZERO);
1183         checkEmpty(c.makePath(EmptyShapeNonZero, TxIdentity), WIND_NON_ZERO);
1184         checkEmpty(c.makePath(EmptyShapeEvenOdd), WIND_EVEN_ODD);
1185         checkEmpty(c.makePath(EmptyShapeEvenOdd, null), WIND_EVEN_ODD);
1186         checkEmpty(c.makePath(EmptyShapeEvenOdd, TxIdentity), WIND_EVEN_ODD);
1187         try {
1188             c.makePath(null);
1189             throw new RuntimeException(c+" allowed null Shape in constructor");
1190         } catch (NullPointerException npe) {
1191             // passes
1192         }
1193         try {
1194             c.makePath(null, TxIdentity);
1195             throw new RuntimeException(c+" allowed null Shape in constructor");
1196         } catch (NullPointerException npe) {
1197             // passes
1198         }
1199 
1200         for (int i = 0; i < TestShapes.length; i++) {
1201             Shape sref = TestShapes[i];
1202             if (verbose) System.out.println("construct testing "+sref);
1203             compare(c, c.makePath(sref), sref, null, 0);
1204             compare(c, c.makePath(sref), sref, TxIdentity, 0);
1205             compare(c, c.makePath(sref, null), sref, null, 0);
1206             compare(c, c.makePath(sref, null), sref, TxIdentity, 0);
1207             compare(c, c.makePath(sref, TxIdentity), sref, null, 0);
1208             compare(c, c.makePath(sref, TxIdentity), sref, TxIdentity, 0);
1209             compare(c, c.makePath(sref, TxComplex), sref, TxComplex,
1210                     c.getRecommendedTxMaxUlp());
1211         }
1212     }
1213 
1214     public static void testPathConstruction(Creator c) {
1215         testPathConstruction(c, LongSampleNonZero);
1216         testPathConstruction(c, LongSampleEvenOdd);
1217     }
1218 
1219     public static void testPathConstruction(Creator c, SampleShape ref) {
1220         if (c.supportsFloatCompose()) {
1221             compare(c, ref.makeFloatPath(c), ref, c.getRecommendedTxMaxUlp());
1222         }
1223         compare(c, ref.makeDoublePath(c), ref, c.getRecommendedTxMaxUlp());
1224     }
1225 
1226     public static void testAppend(Creator c) {
1227         for (int i = 0; i < TestShapes.length; i++) {
1228             Shape sref = TestShapes[i];
1229             if (verbose) System.out.println("append testing "+sref);
1230             PathIterator spi = sref.getPathIterator(null);
1231             Path2D stest = c.makePath(spi.getWindingRule());
1232             stest.append(spi, false);
1233             compare(c, stest, sref, null, 0);
1234             stest.reset();
1235             stest.append(sref, false);
1236             compare(c, stest, sref, null, 0);
1237             stest.reset();
1238             stest.append(sref.getPathIterator(TxComplex), false);
1239             compare(c, stest, sref, TxComplex, 0);
1240             // multiple shape append testing...
1241             if (sref.getBounds2D().isEmpty()) {
1242                 // If the first shape is empty, then we really
1243                 // are not testing multiple appended shapes,
1244                 // we are just testing appending the AppendShape
1245                 // to a null path over and over.
1246                 // Also note that some empty shapes will spit out
1247                 // a single useless SEG_MOVETO that has no affect
1248                 // on the outcome, but it makes duplicating the
1249                 // behavior that Path2D has in that case difficult
1250                 // when the AppenedShape utility class has to
1251                 // iterate the exact same segments.  So, we will
1252                 // just ignore all empty shapes here.
1253                 continue;
1254             }
1255             stest.reset();
1256             stest.append(sref, false);
1257             stest.append(AppendShape, false);
1258             compare(c, stest,
1259                     new AppendedShape(sref, AppendShape, false),
1260                     null, 0);
1261             stest.reset();
1262             stest.append(sref, false);
1263             stest.append(AppendShape, true);
1264             compare(c, stest,
1265                     new AppendedShape(sref, AppendShape, true),
1266                     null, 0);
1267             stest.reset();
1268             stest.append(sref.getPathIterator(null), false);
1269             stest.append(AppendShape.getPathIterator(null), false);
1270             compare(c, stest,
1271                     new AppendedShape(sref, AppendShape, false),
1272                     null, 0);
1273             stest.reset();
1274             stest.append(sref.getPathIterator(null), false);
1275             stest.append(AppendShape.getPathIterator(null), true);
1276             compare(c, stest,
1277                     new AppendedShape(sref, AppendShape, true),
1278                     null, 0);
1279             stest.reset();
1280             stest.append(sref.getPathIterator(TxComplex), false);
1281             stest.append(AppendShape.getPathIterator(TxComplex), false);
1282             compare(c, stest,
1283                     new AppendedShape(sref, AppendShape, false),
1284                     TxComplex, 0);
1285             stest.reset();
1286             stest.append(sref.getPathIterator(TxComplex), false);
1287             stest.append(AppendShape.getPathIterator(TxComplex), true);
1288             compare(c, stest,
1289                     new AppendedShape(sref, AppendShape, true),
1290                     TxComplex, 0);
1291         }
1292     }
1293 
1294     public static void testBounds(Creator c) {
1295         for (int i = 0; i < TestShapes.length; i++) {
1296             Shape sref = TestShapes[i];
1297             if (verbose) System.out.println("bounds testing "+sref);
1298             Shape stest = c.makePath(sref);
1299             checkBounds(c.makePath(sref), sref);
1300         }
1301         testBounds(c, ShortSampleNonZero);
1302         testBounds(c, ShortSampleEvenOdd);
1303         testBounds(c, LongSampleNonZero);
1304         testBounds(c, LongSampleEvenOdd);
1305     }
1306 
1307     public static void testBounds(Creator c, SampleShape ref) {
1308         if (verbose) System.out.println("bounds testing "+ref);
1309         if (c.supportsFloatCompose()) {
1310             checkBounds(ref.makeFloatPath(c), ref);
1311         }
1312         checkBounds(ref.makeDoublePath(c), ref);
1313     }
1314 
1315     public static void testHits(Creator c) {
1316         for (int i = 0; i < TestShapes.length; i++) {
1317             Shape sref = TestShapes[i];
1318             if (verbose) System.out.println("hit testing "+sref);
1319             Shape stest = c.makePath(sref);
1320             checkHits(c.makePath(sref), sref);
1321         }
1322         testHits(c, ShortSampleNonZero);
1323         testHits(c, ShortSampleEvenOdd);
1324         // These take too long to construct the Area for reference testing
1325         //testHits(c, LongSampleNonZero);
1326         //testHits(c, LongSampleEvenOdd);
1327     }
1328 
1329     public static void testHits(Creator c, SampleShape ref) {
1330         if (verbose) System.out.println("hit testing "+ref);
1331         if (c.supportsFloatCompose()) {
1332             checkHits(ref.makeFloatPath(c), ref);
1333         }
1334         checkHits(ref.makeDoublePath(c), ref);
1335     }
1336 
1337     public static void main(String argv[]) {
1338         int limit = (argv.length > 0) ? 10000 : 1;
1339         verbose = (argv.length > 1);
1340         for (int i = 0; i < limit; i++) {
1341             if (limit > 1) {
1342                 System.out.println("loop #"+(i+1));
1343             }
1344             init();
1345             test(new GPCreator());
1346             test(new FltCreator());
1347             test(new DblCreator());
1348         }
1349     }
1350 }
1351