src/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java

Print this page
rev 680 : 8007667: Better image reading
Reviewed-by: prr, jgodinez, omajid

 264     private static native void initReaderIDs(Class iisClass,
 265                                              Class qTableClass,
 266                                              Class huffClass);
 267 
 268     public JPEGImageReader(ImageReaderSpi originator) {
 269         super(originator);
 270         structPointer = initJPEGImageReader();
 271         disposerRecord = new JPEGReaderDisposerRecord(structPointer);
 272         Disposer.addRecord(disposerReferent, disposerRecord);
 273     }
 274 
 275     /** Sets up per-reader C structure and returns a pointer to it. */
 276     private native long initJPEGImageReader();
 277 
 278     /**
 279      * Called by the native code or other classes to signal a warning.
 280      * The code is used to lookup a localized message to be used when
 281      * sending warnings to listeners.
 282      */
 283     protected void warningOccurred(int code) {


 284         if ((code < 0) || (code > MAX_WARNING)){
 285             throw new InternalError("Invalid warning index");
 286         }
 287         processWarningOccurred
 288             ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
 289              Integer.toString(code));



 290     }
 291 
 292     /**
 293      * The library has it's own error facility that emits warning messages.
 294      * This routine is called by the native code when it has already
 295      * formatted a string for output.
 296      * XXX  For truly complete localization of all warning messages,
 297      * the sun_jpeg_output_message routine in the native code should
 298      * send only the codes and parameters to a method here in Java,
 299      * which will then format and send the warnings, using localized
 300      * strings.  This method will have to deal with all the parameters
 301      * and formats (%u with possibly large numbers, %02d, %02x, etc.)
 302      * that actually occur in the JPEG library.  For now, this prevents
 303      * library warnings from being printed to stderr.
 304      */
 305     protected void warningWithMessage(String msg) {


 306         processWarningOccurred(msg);



 307     }
 308 
 309     public void setInput(Object input,
 310                          boolean seekForwardOnly,
 311                          boolean ignoreMetadata)
 312     {
 313         setThreadLock();
 314         try {


 315             super.setInput(input, seekForwardOnly, ignoreMetadata);
 316             this.ignoreMetadata = ignoreMetadata;
 317             resetInternalState();
 318             iis = (ImageInputStream) input; // Always works
 319             setSource(structPointer, iis);
 320         } finally {
 321             clearThreadLock();
 322         }
 323     }
 324 
 325     private native void setSource(long structPointer,
 326                                   ImageInputStream source);



































 327 
 328     private void checkTablesOnly() throws IOException {
 329         if (debug) {
 330             System.out.println("Checking for tables-only image");
 331         }
 332         long savePos = iis.getStreamPosition();
 333         if (debug) {
 334             System.out.println("saved pos is " + savePos);
 335             System.out.println("length is " + iis.length());
 336         }
 337         // Read the first header
 338         boolean tablesOnly = readNativeHeader(true);
 339         if (tablesOnly) {
 340             if (debug) {
 341                 System.out.println("tables-only image found");
 342                 long pos = iis.getStreamPosition();
 343                 System.out.println("pos after return from native is " + pos);
 344             }
 345             // This reads the tables-only image twice, once from C
 346             // and once from Java, but only if ignoreMetadata is false


 358             // Now we are at the first image if there are any, so add it
 359             // to the list
 360             if (hasNextImage()) {
 361                 imagePositions.add(new Long(iis.getStreamPosition()));
 362             }
 363         } else { // Not tables only, so add original pos to the list
 364             imagePositions.add(new Long(savePos));
 365             // And set current image since we've read it now
 366             currentImage = 0;
 367         }
 368         if (seekForwardOnly) {
 369             Long pos = (Long) imagePositions.get(imagePositions.size()-1);
 370             iis.flushBefore(pos.longValue());
 371         }
 372         tablesOnlyChecked = true;
 373     }
 374 
 375     public int getNumImages(boolean allowSearch) throws IOException {
 376         setThreadLock();
 377         try { // locked thread


 378             return getNumImagesOnThread(allowSearch);
 379         } finally {
 380             clearThreadLock();
 381         }
 382     }
 383 
 384     private int getNumImagesOnThread(boolean allowSearch)
 385       throws IOException {
 386         if (numImages != 0) {
 387             return numImages;
 388         }
 389         if (iis == null) {
 390             throw new IllegalStateException("Input not set");
 391         }
 392         if (allowSearch == true) {
 393             if (seekForwardOnly) {
 394                 throw new IllegalStateException(
 395                     "seekForwardOnly and allowSearch can't both be true!");
 396             }
 397             // Otherwise we have to read the entire stream


 557             }
 558             foundFF = (byteval == 0xff) ? true : false;
 559         }
 560         // We hit the end of the stream before we hit an SOI, so no image
 561         iis.reset();
 562         if (debug) {
 563             System.out.println("false");
 564         }
 565         return false;
 566     }
 567 
 568     /**
 569      * Push back the given number of bytes to the input stream.
 570      * Called by the native code at the end of each image so
 571      * that the next one can be identified from Java.
 572      */
 573     private void pushBack(int num) throws IOException {
 574         if (debug) {
 575             System.out.println("pushing back " + num + " bytes");
 576         }


 577         iis.seek(iis.getStreamPosition()-num);
 578         // The buffer is clear after this, so no need to set haveSeeked.



 579     }
 580 
 581     /**
 582      * Reads header information for the given image, if possible.
 583      */
 584     private void readHeader(int imageIndex, boolean reset)
 585         throws IOException {
 586         gotoImage(imageIndex);
 587         readNativeHeader(reset); // Ignore return
 588         currentImage = imageIndex;
 589     }
 590 
 591     private boolean readNativeHeader(boolean reset) throws IOException {
 592         boolean retval = false;
 593         retval = readImageHeader(structPointer, haveSeeked, reset);
 594         haveSeeked = false;
 595         return retval;
 596     }
 597 
 598     /**


 628         this.width = width;
 629         this.height = height;
 630         this.colorSpaceCode = colorSpaceCode;
 631         this.outColorSpaceCode = outColorSpaceCode;
 632         this.numComponents = numComponents;
 633 
 634         if (iccData == null) {
 635             iccCS = null;
 636             return;
 637         }
 638 
 639         ICC_Profile newProfile = null;
 640         try {
 641             newProfile = ICC_Profile.getInstance(iccData);
 642         } catch (IllegalArgumentException e) {
 643             /*
 644              * Color profile data seems to be invalid.
 645              * Ignore this profile.
 646              */
 647             iccCS = null;


 648             warningOccurred(WARNING_IGNORE_INVALID_ICC);



 649 
 650             return;
 651         }
 652         byte[] newData = newProfile.getData();
 653 
 654         ICC_Profile oldProfile = null;
 655         if (iccCS instanceof ICC_ColorSpace) {
 656             oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
 657         }
 658         byte[] oldData = null;
 659         if (oldProfile != null) {
 660             oldData = oldProfile.getData();
 661         }
 662 
 663         /*
 664          * At the moment we can't rely on the ColorSpace.equals()
 665          * and ICC_Profile.equals() because they do not detect
 666          * the case when two profiles are created from same data.
 667          *
 668          * So, we have to do data comparison in order to avoid
 669          * creation of different ColorSpace instances for the same
 670          * embedded data.
 671          */
 672         if (oldData == null ||
 673             !java.util.Arrays.equals(oldData, newData))
 674         {
 675             iccCS = new ICC_ColorSpace(newProfile);
 676         }
 677     }
 678 
 679     public int getWidth(int imageIndex) throws IOException {
 680         setThreadLock();
 681         try {
 682             if (currentImage != imageIndex) {

 683                 readHeader(imageIndex, true);
 684             }
 685             return width;
 686         } finally {
 687             clearThreadLock();
 688         }
 689     }
 690 
 691     public int getHeight(int imageIndex) throws IOException {
 692         setThreadLock();
 693         try {
 694             if (currentImage != imageIndex) {

 695                 readHeader(imageIndex, true);
 696             }
 697             return height;
 698         } finally {
 699             clearThreadLock();
 700         }
 701     }
 702 
 703     /////////// Color Conversion and Image Types
 704 
 705     /**
 706      * Return an ImageTypeSpecifier corresponding to the given
 707      * color space code, or null if the color space is unsupported.
 708      */
 709     private ImageTypeSpecifier getImageType(int code) {
 710         ImageTypeSpecifier ret = null;
 711 
 712         if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
 713             ret = defaultTypes[code];
 714         }
 715         return ret;
 716     }
 717 
 718     public ImageTypeSpecifier getRawImageType(int imageIndex)
 719         throws IOException {
 720         setThreadLock();
 721         try {
 722             if (currentImage != imageIndex) {


 723                 readHeader(imageIndex, true);
 724             }
 725 
 726             // Returns null if it can't be represented
 727             return getImageType(colorSpaceCode);
 728         } finally {
 729             clearThreadLock();
 730         }
 731     }
 732 
 733     public Iterator getImageTypes(int imageIndex)
 734         throws IOException {
 735         setThreadLock();
 736         try {
 737             return getImageTypesOnThread(imageIndex);
 738         } finally {
 739             clearThreadLock();
 740         }
 741     }
 742 
 743     private Iterator getImageTypesOnThread(int imageIndex)
 744         throws IOException {
 745         if (currentImage != imageIndex) {

 746             readHeader(imageIndex, true);
 747         }
 748 
 749         // We return an iterator containing the default, any
 750         // conversions that the library provides, and
 751         // all the other default types with the same number
 752         // of components, as we can do these as a post-process.
 753         // As we convert Rasters rather than images, images
 754         // with alpha cannot be converted in a post-process.
 755 
 756         // If this image can't be interpreted, this method
 757         // returns an empty Iterator.
 758 
 759         // Get the raw ITS, if there is one.  Note that this
 760         // won't always be the same as the default.
 761         ImageTypeSpecifier raw = getImageType(colorSpaceCode);
 762 
 763         // Given the encoded colorspace, build a list of ITS's
 764         // representing outputs you could handle starting
 765         // with the default.


 927             throw new IIOException("Incompatible color conversion");
 928         }
 929     }
 930 
 931     /**
 932      * Set the IJG output space to the given value.  The library will
 933      * perform the appropriate colorspace conversions.
 934      */
 935     private native void setOutColorSpace(long structPointer, int id);
 936 
 937     /////// End of Color Conversion & Image Types
 938 
 939     public ImageReadParam getDefaultReadParam() {
 940         return new JPEGImageReadParam();
 941     }
 942 
 943     public IIOMetadata getStreamMetadata() throws IOException {
 944         setThreadLock();
 945         try {
 946             if (!tablesOnlyChecked) {

 947                 checkTablesOnly();
 948             }
 949             return streamMetadata;
 950         } finally {
 951             clearThreadLock();
 952         }
 953     }
 954 
 955     public IIOMetadata getImageMetadata(int imageIndex)
 956         throws IOException {
 957         setThreadLock();
 958         try {
 959             // imageMetadataIndex will always be either a valid index or
 960             // -1, in which case imageMetadata will not be null.
 961             // So we can leave checking imageIndex for gotoImage.
 962             if ((imageMetadataIndex == imageIndex)
 963                 && (imageMetadata != null)) {
 964                 return imageMetadata;
 965             }
 966 


 967             gotoImage(imageIndex);
 968 
 969             imageMetadata = new JPEGMetadata(false, false, iis, this);
 970 
 971             imageMetadataIndex = imageIndex;
 972 
 973             return imageMetadata;
 974         } finally {
 975             clearThreadLock();
 976         }
 977     }
 978 
 979     public BufferedImage read(int imageIndex, ImageReadParam param)
 980         throws IOException {
 981         setThreadLock();
 982         try {

 983             try {
 984                 readInternal(imageIndex, param, false);
 985             } catch (RuntimeException e) {
 986                 resetLibraryState(structPointer);
 987                 throw e;
 988             } catch (IOException e) {
 989                 resetLibraryState(structPointer);
 990                 throw e;
 991             }
 992 
 993             BufferedImage ret = image;
 994             image = null;  // don't keep a reference here
 995             return ret;
 996         } finally {
 997             clearThreadLock();
 998         }
 999     }
1000 
1001     private Raster readInternal(int imageIndex,
1002                                 ImageReadParam param,


1192         } else {
1193             processImageComplete();
1194         }
1195 
1196         return target;
1197 
1198     }
1199 
1200     /**
1201      * This method is called back from C when the intermediate Raster
1202      * is full.  The parameter indicates the scanline in the target
1203      * Raster to which the intermediate Raster should be copied.
1204      * After the copy, we notify update listeners.
1205      */
1206     private void acceptPixels(int y, boolean progressive) {
1207         if (convert != null) {
1208             convert.filter(raster, raster);
1209         }
1210         target.setRect(destROI.x, destROI.y + y, raster);
1211 


1212         processImageUpdate(image,
1213                            destROI.x, destROI.y+y,
1214                            raster.getWidth(), 1,
1215                            1, 1,
1216                            destinationBands);
1217         if ((y > 0) && (y%progInterval == 0)) {
1218             int height = target.getHeight()-1;
1219             float percentOfPass = ((float)y)/height;
1220             if (progressive) {
1221                 if (knownPassCount != UNKNOWN) {
1222                     processImageProgress((pass + percentOfPass)*100.0F
1223                                          / knownPassCount);
1224                 } else if (maxProgressivePass != Integer.MAX_VALUE) {
1225                     // Use the range of allowed progressive passes
1226                     processImageProgress((pass + percentOfPass)*100.0F
1227                         / (maxProgressivePass - minProgressivePass + 1));
1228                 } else {
1229                     // Assume there are a minimum of MIN_ESTIMATED_PASSES
1230                     // and that there is always one more pass
1231                     // Compute the percentage as the percentage at the end


1245                             * (percentOfPass)/remainingPasses;
1246                         if (debug) {
1247                             System.out.print("pass= " + pass);
1248                             System.out.print(", y= " + y);
1249                             System.out.print(", progInt= " + progInterval);
1250                             System.out.print(", % of pass: " + percentOfPass);
1251                             System.out.print(", rem. passes: "
1252                                              + remainingPasses);
1253                             System.out.print(", prev%: "
1254                                              + previousPassPercentage);
1255                             System.out.print(", %ToDate: " + percentToDate);
1256                             System.out.print(" ");
1257                         }
1258                         processImageProgress(percentToDate*100.0F);
1259                     }
1260                 }
1261             } else {
1262                 processImageProgress(percentOfPass * 100.0F);
1263             }
1264         }



1265     }
1266 
1267     private void initProgressData() {
1268         knownPassCount = UNKNOWN;
1269         pass = 0;
1270         percentToDate = 0.0F;
1271         previousPassPercentage = 0.0F;
1272         progInterval = 0;
1273     }
1274 
1275     private void passStarted (int pass) {


1276         this.pass = pass;
1277         previousPassPercentage = percentToDate;
1278         processPassStarted(image,
1279                            pass,
1280                            minProgressivePass,
1281                            maxProgressivePass,
1282                            0, 0,
1283                            1,1,
1284                            destinationBands);



1285     }
1286 
1287     private void passComplete () {


1288         processPassComplete(image);



1289     }
1290 
1291     void thumbnailStarted(int thumbnailIndex) {


1292         processThumbnailStarted(currentImage, thumbnailIndex);



1293     }
1294 
1295     // Provide access to protected superclass method
1296     void thumbnailProgress(float percentageDone) {


1297         processThumbnailProgress(percentageDone);



1298     }
1299 
1300     // Provide access to protected superclass method
1301     void thumbnailComplete() {


1302         processThumbnailComplete();



1303     }
1304 
1305     /**
1306      * Returns <code>true</code> if the read was aborted.
1307      */
1308     private native boolean readImage(long structPointer,
1309                                      byte [] buffer,
1310                                      int numRasterBands,
1311                                      int [] srcBands,
1312                                      int [] bandSizes,
1313                                      int sourceXOffset, int sourceYOffset,
1314                                      int sourceWidth, int sourceHeight,
1315                                      int periodX, int periodY,
1316                                      JPEGQTable [] abbrevQTables,
1317                                      JPEGHuffmanTable [] abbrevDCHuffmanTables,
1318                                      JPEGHuffmanTable [] abbrevACHuffmanTables,
1319                                      int minProgressivePass,
1320                                      int maxProgressivePass,
1321                                      boolean wantUpdates);
1322 
1323     public void abort() {
1324         setThreadLock();
1325         try {





1326             super.abort();
1327             abortRead(structPointer);
1328         } finally {
1329             clearThreadLock();
1330         }
1331     }
1332 
1333     /** Set the C level abort flag. Keep it atomic for thread safety. */
1334     private native void abortRead(long structPointer);
1335 
1336     /** Resets library state when an exception occurred during a read. */
1337     private native void resetLibraryState(long structPointer);
1338 
1339     public boolean canReadRaster() {
1340         return true;
1341     }
1342 
1343     public Raster readRaster(int imageIndex, ImageReadParam param)
1344         throws IOException {
1345         setThreadLock();
1346         Raster retval = null;
1347         try {

1348             /*
1349              * This could be further optimized by not resetting the dest.
1350              * offset and creating a translated raster in readInternal()
1351              * (see bug 4994702 for more info).
1352              */
1353 
1354             // For Rasters, destination offset is logical, not physical, so
1355             // set it to 0 before calling computeRegions, so that the destination
1356             // region is not clipped.
1357             Point saveDestOffset = null;
1358             if (param != null) {
1359                 saveDestOffset = param.getDestinationOffset();
1360                 param.setDestinationOffset(new Point(0, 0));
1361             }
1362             retval = readInternal(imageIndex, param, true);
1363             // Apply the destination offset, if any, as a logical offset
1364             if (saveDestOffset != null) {
1365                 target = target.createWritableTranslatedChild(saveDestOffset.x,
1366                                                               saveDestOffset.y);
1367             }
1368         } catch (RuntimeException e) {
1369             resetLibraryState(structPointer);
1370             throw e;
1371         } catch (IOException e) {
1372             resetLibraryState(structPointer);
1373             throw e;
1374         } finally {
1375             clearThreadLock();
1376         }
1377         return retval;
1378     }
1379 
1380     public boolean readerSupportsThumbnails() {
1381         return true;
1382     }
1383 
1384     public int getNumThumbnails(int imageIndex) throws IOException {
1385         setThreadLock();
1386         try {


1387             getImageMetadata(imageIndex);  // checks iis state for us
1388             // Now check the jfif segments
1389             JFIFMarkerSegment jfif =
1390                 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1391                 (JFIFMarkerSegment.class, true);
1392             int retval = 0;
1393             if (jfif != null) {
1394                 retval = (jfif.thumb == null) ? 0 : 1;
1395                 retval += jfif.extSegments.size();
1396             }
1397             return retval;
1398         } finally {
1399             clearThreadLock();
1400         }
1401     }
1402 
1403     public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
1404         throws IOException {
1405         setThreadLock();
1406         try {


1407             if ((thumbnailIndex < 0)
1408                 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1409                 throw new IndexOutOfBoundsException("No such thumbnail");
1410             }
1411             // Now we know that there is a jfif segment
1412             JFIFMarkerSegment jfif =
1413                 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1414                 (JFIFMarkerSegment.class, true);
1415             return  jfif.getThumbnailWidth(thumbnailIndex);
1416         } finally {
1417             clearThreadLock();
1418         }
1419     }
1420 
1421     public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
1422         throws IOException {
1423         setThreadLock();
1424         try {


1425             if ((thumbnailIndex < 0)
1426                 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1427                 throw new IndexOutOfBoundsException("No such thumbnail");
1428             }
1429             // Now we know that there is a jfif segment
1430             JFIFMarkerSegment jfif =
1431                 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1432                 (JFIFMarkerSegment.class, true);
1433             return  jfif.getThumbnailHeight(thumbnailIndex);
1434         } finally {
1435             clearThreadLock();
1436         }
1437     }
1438 
1439     public BufferedImage readThumbnail(int imageIndex,
1440                                        int thumbnailIndex)
1441         throws IOException {
1442         setThreadLock();
1443         try {


1444             if ((thumbnailIndex < 0)
1445                 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1446                 throw new IndexOutOfBoundsException("No such thumbnail");
1447             }
1448             // Now we know that there is a jfif segment and that iis is good
1449             JFIFMarkerSegment jfif =
1450                 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1451                 (JFIFMarkerSegment.class, true);
1452             return  jfif.getThumbnail(iis, thumbnailIndex, this);
1453         } finally {
1454             clearThreadLock();
1455         }
1456     }
1457 
1458     private void resetInternalState() {
1459         // reset C structures
1460         resetReader(structPointer);
1461 
1462         // reset local Java structures
1463         numImages = 0;
1464         imagePositions = new ArrayList();
1465         currentImage = -1;
1466         image = null;
1467         raster = null;
1468         target = null;
1469         buffer = null;
1470         destROI = null;
1471         destinationBands = null;
1472         streamMetadata = null;
1473         imageMetadata = null;
1474         imageMetadataIndex = -1;
1475         haveSeeked = false;
1476         tablesOnlyChecked = false;
1477         iccCS = null;
1478         initProgressData();
1479     }
1480 
1481     public void reset() {
1482         setThreadLock();
1483         try {

1484             super.reset();
1485         } finally {
1486             clearThreadLock();
1487         }
1488     }
1489 
1490     private native void resetReader(long structPointer);
1491 
1492     public void dispose() {
1493         setThreadLock();
1494         try {


1495             if (structPointer != 0) {
1496                 disposerRecord.dispose();
1497                 structPointer = 0;
1498             }
1499         } finally {
1500             clearThreadLock();
1501         }
1502     }
1503 
1504     private static native void disposeReader(long structPointer);
1505 
1506     private static class JPEGReaderDisposerRecord implements DisposerRecord {
1507         private long pData;
1508 
1509         public JPEGReaderDisposerRecord(long pData) {
1510             this.pData = pData;
1511         }
1512 
1513         public synchronized void dispose() {
1514             if (pData != 0) {


1534             } else {
1535                 theLockCount ++;
1536             }
1537         } else {
1538             theThread = currThread;
1539             theLockCount = 1;
1540         }
1541     }
1542 
1543     private synchronized void clearThreadLock() {
1544         Thread currThread = Thread.currentThread();
1545         if (theThread == null || theThread != currThread) {
1546             throw new IllegalStateException("Attempt to clear thread lock " +
1547                                             " form wrong thread." +
1548                                             " Locked thread: " + theThread +
1549                                             "; current thread: " + currThread);
1550         }
1551         theLockCount --;
1552         if (theLockCount == 0) {
1553             theThread = null;






























1554         }
1555     }
1556 }

 264     private static native void initReaderIDs(Class iisClass,
 265                                              Class qTableClass,
 266                                              Class huffClass);
 267 
 268     public JPEGImageReader(ImageReaderSpi originator) {
 269         super(originator);
 270         structPointer = initJPEGImageReader();
 271         disposerRecord = new JPEGReaderDisposerRecord(structPointer);
 272         Disposer.addRecord(disposerReferent, disposerRecord);
 273     }
 274 
 275     /** Sets up per-reader C structure and returns a pointer to it. */
 276     private native long initJPEGImageReader();
 277 
 278     /**
 279      * Called by the native code or other classes to signal a warning.
 280      * The code is used to lookup a localized message to be used when
 281      * sending warnings to listeners.
 282      */
 283     protected void warningOccurred(int code) {
 284         cbLock.lock();
 285         try {
 286             if ((code < 0) || (code > MAX_WARNING)){
 287                 throw new InternalError("Invalid warning index");
 288             }
 289             processWarningOccurred
 290                 ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
 291                  Integer.toString(code));
 292         } finally {
 293             cbLock.unlock();
 294         }
 295     }
 296 
 297     /**
 298      * The library has it's own error facility that emits warning messages.
 299      * This routine is called by the native code when it has already
 300      * formatted a string for output.
 301      * XXX  For truly complete localization of all warning messages,
 302      * the sun_jpeg_output_message routine in the native code should
 303      * send only the codes and parameters to a method here in Java,
 304      * which will then format and send the warnings, using localized
 305      * strings.  This method will have to deal with all the parameters
 306      * and formats (%u with possibly large numbers, %02d, %02x, etc.)
 307      * that actually occur in the JPEG library.  For now, this prevents
 308      * library warnings from being printed to stderr.
 309      */
 310     protected void warningWithMessage(String msg) {
 311         cbLock.lock();
 312         try {
 313             processWarningOccurred(msg);
 314         } finally {
 315             cbLock.unlock();
 316         }
 317     }
 318 
 319     public void setInput(Object input,
 320                          boolean seekForwardOnly,
 321                          boolean ignoreMetadata)
 322     {
 323         setThreadLock();
 324         try {
 325             cbLock.check();
 326 
 327             super.setInput(input, seekForwardOnly, ignoreMetadata);
 328             this.ignoreMetadata = ignoreMetadata;
 329             resetInternalState();
 330             iis = (ImageInputStream) input; // Always works
 331             setSource(structPointer);
 332         } finally {
 333             clearThreadLock();
 334         }
 335     }
 336 
 337     /**
 338      * This method is called from native code in order to fill
 339      * native input buffer.
 340      *
 341      * We block any attempt to change the reading state during this
 342      * method, in order to prevent a corruption of the native decoder
 343      * state.
 344      *
 345      * @return number of bytes read from the stream.
 346      */
 347     private int readInputData(byte[] buf, int off, int len) throws IOException {
 348         cbLock.lock();
 349         try {
 350             return iis.read(buf, off, len);
 351         } finally {
 352             cbLock.unlock();
 353         }
 354     }
 355 
 356     /**
 357      * This method is called from the native code in order to
 358      * skip requested number of bytes in the input stream.
 359      *
 360      * @param n
 361      * @return
 362      * @throws IOException
 363      */
 364     private long skipInputBytes(long n) throws IOException {
 365         cbLock.lock();
 366         try {
 367             return iis.skipBytes(n);
 368         } finally {
 369             cbLock.unlock();
 370         }
 371     }
 372 
 373     private native void setSource(long structPointer);
 374 
 375     private void checkTablesOnly() throws IOException {
 376         if (debug) {
 377             System.out.println("Checking for tables-only image");
 378         }
 379         long savePos = iis.getStreamPosition();
 380         if (debug) {
 381             System.out.println("saved pos is " + savePos);
 382             System.out.println("length is " + iis.length());
 383         }
 384         // Read the first header
 385         boolean tablesOnly = readNativeHeader(true);
 386         if (tablesOnly) {
 387             if (debug) {
 388                 System.out.println("tables-only image found");
 389                 long pos = iis.getStreamPosition();
 390                 System.out.println("pos after return from native is " + pos);
 391             }
 392             // This reads the tables-only image twice, once from C
 393             // and once from Java, but only if ignoreMetadata is false


 405             // Now we are at the first image if there are any, so add it
 406             // to the list
 407             if (hasNextImage()) {
 408                 imagePositions.add(new Long(iis.getStreamPosition()));
 409             }
 410         } else { // Not tables only, so add original pos to the list
 411             imagePositions.add(new Long(savePos));
 412             // And set current image since we've read it now
 413             currentImage = 0;
 414         }
 415         if (seekForwardOnly) {
 416             Long pos = (Long) imagePositions.get(imagePositions.size()-1);
 417             iis.flushBefore(pos.longValue());
 418         }
 419         tablesOnlyChecked = true;
 420     }
 421 
 422     public int getNumImages(boolean allowSearch) throws IOException {
 423         setThreadLock();
 424         try { // locked thread
 425             cbLock.check();
 426 
 427             return getNumImagesOnThread(allowSearch);
 428         } finally {
 429             clearThreadLock();
 430         }
 431     }
 432 
 433     private int getNumImagesOnThread(boolean allowSearch)
 434       throws IOException {
 435         if (numImages != 0) {
 436             return numImages;
 437         }
 438         if (iis == null) {
 439             throw new IllegalStateException("Input not set");
 440         }
 441         if (allowSearch == true) {
 442             if (seekForwardOnly) {
 443                 throw new IllegalStateException(
 444                     "seekForwardOnly and allowSearch can't both be true!");
 445             }
 446             // Otherwise we have to read the entire stream


 606             }
 607             foundFF = (byteval == 0xff) ? true : false;
 608         }
 609         // We hit the end of the stream before we hit an SOI, so no image
 610         iis.reset();
 611         if (debug) {
 612             System.out.println("false");
 613         }
 614         return false;
 615     }
 616 
 617     /**
 618      * Push back the given number of bytes to the input stream.
 619      * Called by the native code at the end of each image so
 620      * that the next one can be identified from Java.
 621      */
 622     private void pushBack(int num) throws IOException {
 623         if (debug) {
 624             System.out.println("pushing back " + num + " bytes");
 625         }
 626         cbLock.lock();
 627         try {
 628             iis.seek(iis.getStreamPosition()-num);
 629             // The buffer is clear after this, so no need to set haveSeeked.
 630         } finally {
 631             cbLock.unlock();
 632         }
 633     }
 634 
 635     /**
 636      * Reads header information for the given image, if possible.
 637      */
 638     private void readHeader(int imageIndex, boolean reset)
 639         throws IOException {
 640         gotoImage(imageIndex);
 641         readNativeHeader(reset); // Ignore return
 642         currentImage = imageIndex;
 643     }
 644 
 645     private boolean readNativeHeader(boolean reset) throws IOException {
 646         boolean retval = false;
 647         retval = readImageHeader(structPointer, haveSeeked, reset);
 648         haveSeeked = false;
 649         return retval;
 650     }
 651 
 652     /**


 682         this.width = width;
 683         this.height = height;
 684         this.colorSpaceCode = colorSpaceCode;
 685         this.outColorSpaceCode = outColorSpaceCode;
 686         this.numComponents = numComponents;
 687 
 688         if (iccData == null) {
 689             iccCS = null;
 690             return;
 691         }
 692 
 693         ICC_Profile newProfile = null;
 694         try {
 695             newProfile = ICC_Profile.getInstance(iccData);
 696         } catch (IllegalArgumentException e) {
 697             /*
 698              * Color profile data seems to be invalid.
 699              * Ignore this profile.
 700              */
 701             iccCS = null;
 702             cbLock.lock();
 703             try {
 704                 warningOccurred(WARNING_IGNORE_INVALID_ICC);
 705             } finally {
 706                 cbLock.unlock();
 707             }
 708 
 709             return;
 710         }
 711         byte[] newData = newProfile.getData();
 712 
 713         ICC_Profile oldProfile = null;
 714         if (iccCS instanceof ICC_ColorSpace) {
 715             oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
 716         }
 717         byte[] oldData = null;
 718         if (oldProfile != null) {
 719             oldData = oldProfile.getData();
 720         }
 721 
 722         /*
 723          * At the moment we can't rely on the ColorSpace.equals()
 724          * and ICC_Profile.equals() because they do not detect
 725          * the case when two profiles are created from same data.
 726          *
 727          * So, we have to do data comparison in order to avoid
 728          * creation of different ColorSpace instances for the same
 729          * embedded data.
 730          */
 731         if (oldData == null ||
 732             !java.util.Arrays.equals(oldData, newData))
 733         {
 734             iccCS = new ICC_ColorSpace(newProfile);
 735         }
 736     }
 737 
 738     public int getWidth(int imageIndex) throws IOException {
 739         setThreadLock();
 740         try {
 741             if (currentImage != imageIndex) {
 742                 cbLock.check();
 743                 readHeader(imageIndex, true);
 744             }
 745             return width;
 746         } finally {
 747             clearThreadLock();
 748         }
 749     }
 750 
 751     public int getHeight(int imageIndex) throws IOException {
 752         setThreadLock();
 753         try {
 754             if (currentImage != imageIndex) {
 755                 cbLock.check();
 756                 readHeader(imageIndex, true);
 757             }
 758             return height;
 759         } finally {
 760             clearThreadLock();
 761         }
 762     }
 763 
 764     /////////// Color Conversion and Image Types
 765 
 766     /**
 767      * Return an ImageTypeSpecifier corresponding to the given
 768      * color space code, or null if the color space is unsupported.
 769      */
 770     private ImageTypeSpecifier getImageType(int code) {
 771         ImageTypeSpecifier ret = null;
 772 
 773         if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
 774             ret = defaultTypes[code];
 775         }
 776         return ret;
 777     }
 778 
 779     public ImageTypeSpecifier getRawImageType(int imageIndex)
 780         throws IOException {
 781         setThreadLock();
 782         try {
 783             if (currentImage != imageIndex) {
 784                 cbLock.check();
 785 
 786                 readHeader(imageIndex, true);
 787             }
 788 
 789             // Returns null if it can't be represented
 790             return getImageType(colorSpaceCode);
 791         } finally {
 792             clearThreadLock();
 793         }
 794     }
 795 
 796     public Iterator getImageTypes(int imageIndex)
 797         throws IOException {
 798         setThreadLock();
 799         try {
 800             return getImageTypesOnThread(imageIndex);
 801         } finally {
 802             clearThreadLock();
 803         }
 804     }
 805 
 806     private Iterator getImageTypesOnThread(int imageIndex)
 807         throws IOException {
 808         if (currentImage != imageIndex) {
 809             cbLock.check();
 810             readHeader(imageIndex, true);
 811         }
 812 
 813         // We return an iterator containing the default, any
 814         // conversions that the library provides, and
 815         // all the other default types with the same number
 816         // of components, as we can do these as a post-process.
 817         // As we convert Rasters rather than images, images
 818         // with alpha cannot be converted in a post-process.
 819 
 820         // If this image can't be interpreted, this method
 821         // returns an empty Iterator.
 822 
 823         // Get the raw ITS, if there is one.  Note that this
 824         // won't always be the same as the default.
 825         ImageTypeSpecifier raw = getImageType(colorSpaceCode);
 826 
 827         // Given the encoded colorspace, build a list of ITS's
 828         // representing outputs you could handle starting
 829         // with the default.


 991             throw new IIOException("Incompatible color conversion");
 992         }
 993     }
 994 
 995     /**
 996      * Set the IJG output space to the given value.  The library will
 997      * perform the appropriate colorspace conversions.
 998      */
 999     private native void setOutColorSpace(long structPointer, int id);
1000 
1001     /////// End of Color Conversion & Image Types
1002 
1003     public ImageReadParam getDefaultReadParam() {
1004         return new JPEGImageReadParam();
1005     }
1006 
1007     public IIOMetadata getStreamMetadata() throws IOException {
1008         setThreadLock();
1009         try {
1010             if (!tablesOnlyChecked) {
1011                 cbLock.check();
1012                 checkTablesOnly();
1013             }
1014             return streamMetadata;
1015         } finally {
1016             clearThreadLock();
1017         }
1018     }
1019 
1020     public IIOMetadata getImageMetadata(int imageIndex)
1021         throws IOException {
1022         setThreadLock();
1023         try {
1024             // imageMetadataIndex will always be either a valid index or
1025             // -1, in which case imageMetadata will not be null.
1026             // So we can leave checking imageIndex for gotoImage.
1027             if ((imageMetadataIndex == imageIndex)
1028                 && (imageMetadata != null)) {
1029                 return imageMetadata;
1030             }
1031 
1032             cbLock.check();
1033 
1034             gotoImage(imageIndex);
1035 
1036             imageMetadata = new JPEGMetadata(false, false, iis, this);
1037 
1038             imageMetadataIndex = imageIndex;
1039 
1040             return imageMetadata;
1041         } finally {
1042             clearThreadLock();
1043         }
1044     }
1045 
1046     public BufferedImage read(int imageIndex, ImageReadParam param)
1047         throws IOException {
1048         setThreadLock();
1049         try {
1050             cbLock.check();
1051             try {
1052                 readInternal(imageIndex, param, false);
1053             } catch (RuntimeException e) {
1054                 resetLibraryState(structPointer);
1055                 throw e;
1056             } catch (IOException e) {
1057                 resetLibraryState(structPointer);
1058                 throw e;
1059             }
1060 
1061             BufferedImage ret = image;
1062             image = null;  // don't keep a reference here
1063             return ret;
1064         } finally {
1065             clearThreadLock();
1066         }
1067     }
1068 
1069     private Raster readInternal(int imageIndex,
1070                                 ImageReadParam param,


1260         } else {
1261             processImageComplete();
1262         }
1263 
1264         return target;
1265 
1266     }
1267 
1268     /**
1269      * This method is called back from C when the intermediate Raster
1270      * is full.  The parameter indicates the scanline in the target
1271      * Raster to which the intermediate Raster should be copied.
1272      * After the copy, we notify update listeners.
1273      */
1274     private void acceptPixels(int y, boolean progressive) {
1275         if (convert != null) {
1276             convert.filter(raster, raster);
1277         }
1278         target.setRect(destROI.x, destROI.y + y, raster);
1279 
1280         cbLock.lock();
1281         try {
1282             processImageUpdate(image,
1283                                destROI.x, destROI.y+y,
1284                                raster.getWidth(), 1,
1285                                1, 1,
1286                                destinationBands);
1287             if ((y > 0) && (y%progInterval == 0)) {
1288                 int height = target.getHeight()-1;
1289                 float percentOfPass = ((float)y)/height;
1290                 if (progressive) {
1291                     if (knownPassCount != UNKNOWN) {
1292                         processImageProgress((pass + percentOfPass)*100.0F
1293                                              / knownPassCount);
1294                     } else if (maxProgressivePass != Integer.MAX_VALUE) {
1295                         // Use the range of allowed progressive passes
1296                         processImageProgress((pass + percentOfPass)*100.0F
1297                                              / (maxProgressivePass - minProgressivePass + 1));
1298                     } else {
1299                         // Assume there are a minimum of MIN_ESTIMATED_PASSES
1300                         // and that there is always one more pass
1301                         // Compute the percentage as the percentage at the end


1315                                 * (percentOfPass)/remainingPasses;
1316                             if (debug) {
1317                                 System.out.print("pass= " + pass);
1318                                 System.out.print(", y= " + y);
1319                                 System.out.print(", progInt= " + progInterval);
1320                                 System.out.print(", % of pass: " + percentOfPass);
1321                                 System.out.print(", rem. passes: "
1322                                                  + remainingPasses);
1323                                 System.out.print(", prev%: "
1324                                                  + previousPassPercentage);
1325                                 System.out.print(", %ToDate: " + percentToDate);
1326                                 System.out.print(" ");
1327                             }
1328                             processImageProgress(percentToDate*100.0F);
1329                         }
1330                     }
1331                 } else {
1332                     processImageProgress(percentOfPass * 100.0F);
1333                 }
1334             }
1335         } finally {
1336             cbLock.unlock();
1337         }
1338     }
1339 
1340     private void initProgressData() {
1341         knownPassCount = UNKNOWN;
1342         pass = 0;
1343         percentToDate = 0.0F;
1344         previousPassPercentage = 0.0F;
1345         progInterval = 0;
1346     }
1347 
1348     private void passStarted (int pass) {
1349         cbLock.lock();
1350         try {
1351             this.pass = pass;
1352             previousPassPercentage = percentToDate;
1353             processPassStarted(image,
1354                                pass,
1355                                minProgressivePass,
1356                                maxProgressivePass,
1357                                0, 0,
1358                                1,1,
1359                                destinationBands);
1360         } finally {
1361             cbLock.unlock();
1362         }
1363     }
1364 
1365     private void passComplete () {
1366         cbLock.lock();
1367         try {
1368             processPassComplete(image);
1369         } finally {
1370             cbLock.unlock();
1371         }
1372     }
1373 
1374     void thumbnailStarted(int thumbnailIndex) {
1375         cbLock.lock();
1376         try {
1377             processThumbnailStarted(currentImage, thumbnailIndex);
1378         } finally {
1379             cbLock.unlock();
1380         }
1381     }
1382 
1383     // Provide access to protected superclass method
1384     void thumbnailProgress(float percentageDone) {
1385         cbLock.lock();
1386         try {
1387             processThumbnailProgress(percentageDone);
1388         } finally {
1389             cbLock.unlock();
1390         }
1391     }
1392 
1393     // Provide access to protected superclass method
1394     void thumbnailComplete() {
1395         cbLock.lock();
1396         try {
1397             processThumbnailComplete();
1398         } finally {
1399             cbLock.unlock();
1400         }
1401     }
1402 
1403     /**
1404      * Returns <code>true</code> if the read was aborted.
1405      */
1406     private native boolean readImage(long structPointer,
1407                                      byte [] buffer,
1408                                      int numRasterBands,
1409                                      int [] srcBands,
1410                                      int [] bandSizes,
1411                                      int sourceXOffset, int sourceYOffset,
1412                                      int sourceWidth, int sourceHeight,
1413                                      int periodX, int periodY,
1414                                      JPEGQTable [] abbrevQTables,
1415                                      JPEGHuffmanTable [] abbrevDCHuffmanTables,
1416                                      JPEGHuffmanTable [] abbrevACHuffmanTables,
1417                                      int minProgressivePass,
1418                                      int maxProgressivePass,
1419                                      boolean wantUpdates);
1420 
1421     public void abort() {
1422         setThreadLock();
1423         try {
1424             /**
1425              * NB: we do not check the call back lock here,
1426              * we allow to abort the reader any time.
1427              */
1428 
1429             super.abort();
1430             abortRead(structPointer);
1431         } finally {
1432             clearThreadLock();
1433         }
1434     }
1435 
1436     /** Set the C level abort flag. Keep it atomic for thread safety. */
1437     private native void abortRead(long structPointer);
1438 
1439     /** Resets library state when an exception occurred during a read. */
1440     private native void resetLibraryState(long structPointer);
1441 
1442     public boolean canReadRaster() {
1443         return true;
1444     }
1445 
1446     public Raster readRaster(int imageIndex, ImageReadParam param)
1447         throws IOException {
1448         setThreadLock();
1449         Raster retval = null;
1450         try {
1451             cbLock.check();
1452             /*
1453              * This could be further optimized by not resetting the dest.
1454              * offset and creating a translated raster in readInternal()
1455              * (see bug 4994702 for more info).
1456              */
1457 
1458             // For Rasters, destination offset is logical, not physical, so
1459             // set it to 0 before calling computeRegions, so that the destination
1460             // region is not clipped.
1461             Point saveDestOffset = null;
1462             if (param != null) {
1463                 saveDestOffset = param.getDestinationOffset();
1464                 param.setDestinationOffset(new Point(0, 0));
1465             }
1466             retval = readInternal(imageIndex, param, true);
1467             // Apply the destination offset, if any, as a logical offset
1468             if (saveDestOffset != null) {
1469                 target = target.createWritableTranslatedChild(saveDestOffset.x,
1470                                                               saveDestOffset.y);
1471             }
1472         } catch (RuntimeException e) {
1473             resetLibraryState(structPointer);
1474             throw e;
1475         } catch (IOException e) {
1476             resetLibraryState(structPointer);
1477             throw e;
1478         } finally {
1479             clearThreadLock();
1480         }
1481         return retval;
1482     }
1483 
1484     public boolean readerSupportsThumbnails() {
1485         return true;
1486     }
1487 
1488     public int getNumThumbnails(int imageIndex) throws IOException {
1489         setThreadLock();
1490         try {
1491             cbLock.check();
1492 
1493             getImageMetadata(imageIndex);  // checks iis state for us
1494             // Now check the jfif segments
1495             JFIFMarkerSegment jfif =
1496                 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1497                 (JFIFMarkerSegment.class, true);
1498             int retval = 0;
1499             if (jfif != null) {
1500                 retval = (jfif.thumb == null) ? 0 : 1;
1501                 retval += jfif.extSegments.size();
1502             }
1503             return retval;
1504         } finally {
1505             clearThreadLock();
1506         }
1507     }
1508 
1509     public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
1510         throws IOException {
1511         setThreadLock();
1512         try {
1513             cbLock.check();
1514 
1515             if ((thumbnailIndex < 0)
1516                 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1517                 throw new IndexOutOfBoundsException("No such thumbnail");
1518             }
1519             // Now we know that there is a jfif segment
1520             JFIFMarkerSegment jfif =
1521                 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1522                 (JFIFMarkerSegment.class, true);
1523             return  jfif.getThumbnailWidth(thumbnailIndex);
1524         } finally {
1525             clearThreadLock();
1526         }
1527     }
1528 
1529     public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
1530         throws IOException {
1531         setThreadLock();
1532         try {
1533             cbLock.check();
1534 
1535             if ((thumbnailIndex < 0)
1536                 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1537                 throw new IndexOutOfBoundsException("No such thumbnail");
1538             }
1539             // Now we know that there is a jfif segment
1540             JFIFMarkerSegment jfif =
1541                 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1542                 (JFIFMarkerSegment.class, true);
1543             return  jfif.getThumbnailHeight(thumbnailIndex);
1544         } finally {
1545             clearThreadLock();
1546         }
1547     }
1548 
1549     public BufferedImage readThumbnail(int imageIndex,
1550                                        int thumbnailIndex)
1551         throws IOException {
1552         setThreadLock();
1553         try {
1554             cbLock.check();
1555 
1556             if ((thumbnailIndex < 0)
1557                 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1558                 throw new IndexOutOfBoundsException("No such thumbnail");
1559             }
1560             // Now we know that there is a jfif segment and that iis is good
1561             JFIFMarkerSegment jfif =
1562                 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1563                 (JFIFMarkerSegment.class, true);
1564             return  jfif.getThumbnail(iis, thumbnailIndex, this);
1565         } finally {
1566             clearThreadLock();
1567         }
1568     }
1569 
1570     private void resetInternalState() {
1571         // reset C structures
1572         resetReader(structPointer);
1573 
1574         // reset local Java structures
1575         numImages = 0;
1576         imagePositions = new ArrayList();
1577         currentImage = -1;
1578         image = null;
1579         raster = null;
1580         target = null;
1581         buffer = null;
1582         destROI = null;
1583         destinationBands = null;
1584         streamMetadata = null;
1585         imageMetadata = null;
1586         imageMetadataIndex = -1;
1587         haveSeeked = false;
1588         tablesOnlyChecked = false;
1589         iccCS = null;
1590         initProgressData();
1591     }
1592 
1593     public void reset() {
1594         setThreadLock();
1595         try {
1596             cbLock.check();
1597             super.reset();
1598         } finally {
1599             clearThreadLock();
1600         }
1601     }
1602 
1603     private native void resetReader(long structPointer);
1604 
1605     public void dispose() {
1606         setThreadLock();
1607         try {
1608             cbLock.check();
1609 
1610             if (structPointer != 0) {
1611                 disposerRecord.dispose();
1612                 structPointer = 0;
1613             }
1614         } finally {
1615             clearThreadLock();
1616         }
1617     }
1618 
1619     private static native void disposeReader(long structPointer);
1620 
1621     private static class JPEGReaderDisposerRecord implements DisposerRecord {
1622         private long pData;
1623 
1624         public JPEGReaderDisposerRecord(long pData) {
1625             this.pData = pData;
1626         }
1627 
1628         public synchronized void dispose() {
1629             if (pData != 0) {


1649             } else {
1650                 theLockCount ++;
1651             }
1652         } else {
1653             theThread = currThread;
1654             theLockCount = 1;
1655         }
1656     }
1657 
1658     private synchronized void clearThreadLock() {
1659         Thread currThread = Thread.currentThread();
1660         if (theThread == null || theThread != currThread) {
1661             throw new IllegalStateException("Attempt to clear thread lock " +
1662                                             " form wrong thread." +
1663                                             " Locked thread: " + theThread +
1664                                             "; current thread: " + currThread);
1665         }
1666         theLockCount --;
1667         if (theLockCount == 0) {
1668             theThread = null;
1669         }
1670     }
1671 
1672     private CallBackLock cbLock = new CallBackLock();
1673 
1674     private static class CallBackLock {
1675 
1676         private State lockState;
1677 
1678         CallBackLock() {
1679             lockState = State.Unlocked;
1680         }
1681 
1682         void check() {
1683             if (lockState != State.Unlocked) {
1684                 throw new IllegalStateException("Access to the reader is not allowed");
1685             }
1686         }
1687 
1688         private void lock() {
1689             lockState = State.Locked;
1690         }
1691 
1692         private void unlock() {
1693             lockState = State.Unlocked;
1694         }
1695 
1696         private static enum State {
1697             Unlocked,
1698             Locked
1699         }
1700     }
1701 }