1 /*
2 * Copyright (c) 2000, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
177 java.security.AccessController.doPrivileged(
178 new java.security.PrivilegedAction<Void>() {
179 public Void run() {
180 System.loadLibrary("javajpeg");
181 return null;
182 }
183 });
184 initWriterIDs(JPEGQTable.class,
185 JPEGHuffmanTable.class);
186 }
187
188 //////// Public API
189
190 public JPEGImageWriter(ImageWriterSpi originator) {
191 super(originator);
192 structPointer = initJPEGImageWriter();
193 disposerRecord = new JPEGWriterDisposerRecord(structPointer);
194 Disposer.addRecord(disposerReferent, disposerRecord);
195 }
196
197 public void setOutput(Object output) {
198 setThreadLock();
199 try {
200 cbLock.check();
201
202 super.setOutput(output); // validates output
203 resetInternalState();
204 ios = (ImageOutputStream) output; // so this will always work
205 // Set the native destination
206 setDest(structPointer);
207 } finally {
208 clearThreadLock();
209 }
210 }
211
212 public ImageWriteParam getDefaultWriteParam() {
213 return new JPEGImageWriteParam(null);
214 }
215
216 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
217 setThreadLock();
218 try {
219 return new JPEGMetadata(param, this);
220 } finally {
221 clearThreadLock();
222 }
223 }
224
225 public IIOMetadata
226 getDefaultImageMetadata(ImageTypeSpecifier imageType,
227 ImageWriteParam param) {
228 setThreadLock();
229 try {
230 return new JPEGMetadata(imageType, param, this);
231 } finally {
232 clearThreadLock();
233 }
234 }
235
236 public IIOMetadata convertStreamMetadata(IIOMetadata inData,
237 ImageWriteParam param) {
238 // There isn't much we can do. If it's one of ours, then
239 // return it. Otherwise just return null. We use it only
240 // for tables, so we can't get a default and modify it,
241 // as this will usually not be what is intended.
242 if (inData instanceof JPEGMetadata) {
243 JPEGMetadata jpegData = (JPEGMetadata) inData;
244 if (jpegData.isStream) {
245 return inData;
246 }
247 }
248 return null;
249 }
250
251 public IIOMetadata
252 convertImageMetadata(IIOMetadata inData,
253 ImageTypeSpecifier imageType,
254 ImageWriteParam param) {
255 setThreadLock();
256 try {
257 return convertImageMetadataOnThread(inData, imageType, param);
258 } finally {
259 clearThreadLock();
260 }
261 }
262
263 private IIOMetadata
264 convertImageMetadataOnThread(IIOMetadata inData,
265 ImageTypeSpecifier imageType,
266 ImageWriteParam param) {
267 // If it's one of ours, just return it
268 if (inData instanceof JPEGMetadata) {
269 JPEGMetadata jpegData = (JPEGMetadata) inData;
270 if (!jpegData.isStream) {
282 IIOMetadataFormatImpl.standardMetadataFormatName;
283 Node tree = inData.getAsTree(formatName);
284 if (tree != null) {
285 JPEGMetadata jpegData = new JPEGMetadata(imageType,
286 param,
287 this);
288 try {
289 jpegData.setFromTree(formatName, tree);
290 } catch (IIOInvalidTreeException e) {
291 // Other plug-in generates bogus standard tree
292 // XXX Maybe this should put out a warning?
293 return null;
294 }
295
296 return jpegData;
297 }
298 }
299 return null;
300 }
301
302 public int getNumThumbnailsSupported(ImageTypeSpecifier imageType,
303 ImageWriteParam param,
304 IIOMetadata streamMetadata,
305 IIOMetadata imageMetadata) {
306 // Check whether sufficient data is available.
307 if (imageType == null && imageMetadata == null) {
308 // The method has been invoked with insufficient data. Henceforth
309 // we return -1 as recommended by ImageWriter specification.
310 return -1;
311 }
312
313 // Check if the image type and metadata are JFIF compatible.
314 if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
315 return Integer.MAX_VALUE;
316 }
317 return 0;
318 }
319
320 static final Dimension [] preferredThumbSizes = {new Dimension(1, 1),
321 new Dimension(255, 255)};
322
323 public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType,
324 ImageWriteParam param,
325 IIOMetadata streamMetadata,
326 IIOMetadata imageMetadata) {
327 if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
328 return preferredThumbSizes.clone();
329 }
330 return null;
331 }
332
333 private boolean jfifOK(ImageTypeSpecifier imageType,
334 ImageWriteParam param,
335 IIOMetadata streamMetadata,
336 IIOMetadata imageMetadata) {
337 // If the image type and metadata are JFIF compatible, return true
338 if ((imageType != null) &&
339 (!JPEG.isJFIFcompliant(imageType, true))) {
340 return false;
341 }
342 if (imageMetadata != null) {
343 JPEGMetadata metadata = null;
344 if (imageMetadata instanceof JPEGMetadata) {
345 metadata = (JPEGMetadata) imageMetadata;
346 } else {
347 metadata = (JPEGMetadata)convertImageMetadata(imageMetadata,
348 imageType,
349 param);
350 }
351 // metadata must have a jfif node
352 if (metadata.findMarkerSegment
353 (JFIFMarkerSegment.class, true) == null){
354 return false;
355 }
356 }
357 return true;
358 }
359
360 public boolean canWriteRasters() {
361 return true;
362 }
363
364 public void write(IIOMetadata streamMetadata,
365 IIOImage image,
366 ImageWriteParam param) throws IOException {
367 setThreadLock();
368 try {
369 cbLock.check();
370
371 writeOnThread(streamMetadata, image, param);
372 } finally {
373 clearThreadLock();
374 }
375 }
376
377 private void writeOnThread(IIOMetadata streamMetadata,
378 IIOImage image,
379 ImageWriteParam param) throws IOException {
380
381 if (ios == null) {
382 throw new IllegalStateException("Output has not been set!");
383 }
1032 cbLock.lock();
1033 try {
1034 if (aborted) {
1035 processWriteAborted();
1036 } else {
1037 processImageComplete();
1038 }
1039
1040 ios.flush();
1041 } finally {
1042 cbLock.unlock();
1043 }
1044 currentImage++; // After a successful write
1045 }
1046
1047 @Override
1048 public boolean canWriteSequence() {
1049 return true;
1050 }
1051
1052 public void prepareWriteSequence(IIOMetadata streamMetadata)
1053 throws IOException {
1054 setThreadLock();
1055 try {
1056 cbLock.check();
1057
1058 prepareWriteSequenceOnThread(streamMetadata);
1059 } finally {
1060 clearThreadLock();
1061 }
1062 }
1063
1064 private void prepareWriteSequenceOnThread(IIOMetadata streamMetadata)
1065 throws IOException {
1066 if (ios == null) {
1067 throw new IllegalStateException("Output has not been set!");
1068 }
1069
1070 /*
1071 * from jpeg_metadata.html:
1113 streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
1114 }
1115 streamACHuffmanTables =
1116 collectHTablesFromMetadata(jmeta, false);
1117 if (streamACHuffmanTables == null) {
1118 streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
1119 }
1120
1121 // Now write them out
1122 writeTables(structPointer,
1123 streamQTables,
1124 streamDCHuffmanTables,
1125 streamACHuffmanTables);
1126 } else {
1127 throw new IIOException("Stream metadata must be JPEG metadata");
1128 }
1129 }
1130 sequencePrepared = true;
1131 }
1132
1133 public void writeToSequence(IIOImage image, ImageWriteParam param)
1134 throws IOException {
1135 setThreadLock();
1136 try {
1137 cbLock.check();
1138
1139 if (sequencePrepared == false) {
1140 throw new IllegalStateException("sequencePrepared not called!");
1141 }
1142 // In the case of JPEG this does nothing different from write
1143 write(null, image, param);
1144 } finally {
1145 clearThreadLock();
1146 }
1147 }
1148
1149 public void endWriteSequence() throws IOException {
1150 setThreadLock();
1151 try {
1152 cbLock.check();
1153
1154 if (sequencePrepared == false) {
1155 throw new IllegalStateException("sequencePrepared not called!");
1156 }
1157 sequencePrepared = false;
1158 } finally {
1159 clearThreadLock();
1160 }
1161 }
1162
1163 public synchronized void abort() {
1164 setThreadLock();
1165 try {
1166 /**
1167 * NB: we do not check the call back lock here, we allow to abort
1168 * the reader any time.
1169 */
1170 super.abort();
1171 abortWrite(structPointer);
1172 } finally {
1173 clearThreadLock();
1174 }
1175 }
1176
1177 @Override
1178 protected synchronized void clearAbortRequest() {
1179 setThreadLock();
1180 try {
1181 cbLock.check();
1182 if (abortRequested()) {
1187 setDest(structPointer);
1188 }
1189 } finally {
1190 clearThreadLock();
1191 }
1192 }
1193
1194 private void resetInternalState() {
1195 // reset C structures
1196 resetWriter(structPointer);
1197
1198 // reset local Java structures
1199 srcRas = null;
1200 raster = null;
1201 convertTosRGB = false;
1202 currentImage = 0;
1203 numScans = 0;
1204 metadata = null;
1205 }
1206
1207 public void reset() {
1208 setThreadLock();
1209 try {
1210 cbLock.check();
1211
1212 super.reset();
1213 } finally {
1214 clearThreadLock();
1215 }
1216 }
1217
1218 public void dispose() {
1219 setThreadLock();
1220 try {
1221 cbLock.check();
1222
1223 if (structPointer != 0) {
1224 disposerRecord.dispose();
1225 structPointer = 0;
1226 }
1227 } finally {
1228 clearThreadLock();
1229 }
1230 }
1231
1232 ////////// End of public API
1233
1234 ///////// Package-access API
1235
1236 /**
1237 * Called by the native code or other classes to signal a warning.
1718 }
1719 }
1720 }
1721
1722 /** Aborts the current write in the native code */
1723 private native void abortWrite(long structPointer);
1724
1725 /** Resets native structures */
1726 private native void resetWriter(long structPointer);
1727
1728 /** Releases native structures */
1729 private static native void disposeWriter(long structPointer);
1730
1731 private static class JPEGWriterDisposerRecord implements DisposerRecord {
1732 private long pData;
1733
1734 public JPEGWriterDisposerRecord(long pData) {
1735 this.pData = pData;
1736 }
1737
1738 public synchronized void dispose() {
1739 if (pData != 0) {
1740 disposeWriter(pData);
1741 pData = 0;
1742 }
1743 }
1744 }
1745
1746 /**
1747 * This method is called from native code in order to write encoder
1748 * output to the destination.
1749 *
1750 * We block any attempt to change the writer state during this
1751 * method, in order to prevent a corruption of the native encoder
1752 * state.
1753 */
1754 private void writeOutputData(byte[] data, int offset, int len)
1755 throws IOException
1756 {
1757 cbLock.lock();
|
1 /*
2 * Copyright (c) 2000, 2020, 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
177 java.security.AccessController.doPrivileged(
178 new java.security.PrivilegedAction<Void>() {
179 public Void run() {
180 System.loadLibrary("javajpeg");
181 return null;
182 }
183 });
184 initWriterIDs(JPEGQTable.class,
185 JPEGHuffmanTable.class);
186 }
187
188 //////// Public API
189
190 public JPEGImageWriter(ImageWriterSpi originator) {
191 super(originator);
192 structPointer = initJPEGImageWriter();
193 disposerRecord = new JPEGWriterDisposerRecord(structPointer);
194 Disposer.addRecord(disposerReferent, disposerRecord);
195 }
196
197 @Override
198 public void setOutput(Object output) {
199 setThreadLock();
200 try {
201 cbLock.check();
202
203 super.setOutput(output); // validates output
204 resetInternalState();
205 ios = (ImageOutputStream) output; // so this will always work
206 // Set the native destination
207 setDest(structPointer);
208 } finally {
209 clearThreadLock();
210 }
211 }
212
213 @Override
214 public ImageWriteParam getDefaultWriteParam() {
215 return new JPEGImageWriteParam(null);
216 }
217
218 @Override
219 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
220 setThreadLock();
221 try {
222 return new JPEGMetadata(param, this);
223 } finally {
224 clearThreadLock();
225 }
226 }
227
228 @Override
229 public IIOMetadata
230 getDefaultImageMetadata(ImageTypeSpecifier imageType,
231 ImageWriteParam param) {
232 setThreadLock();
233 try {
234 return new JPEGMetadata(imageType, param, this);
235 } finally {
236 clearThreadLock();
237 }
238 }
239
240 @Override
241 public IIOMetadata convertStreamMetadata(IIOMetadata inData,
242 ImageWriteParam param) {
243 // There isn't much we can do. If it's one of ours, then
244 // return it. Otherwise just return null. We use it only
245 // for tables, so we can't get a default and modify it,
246 // as this will usually not be what is intended.
247 if (inData instanceof JPEGMetadata) {
248 JPEGMetadata jpegData = (JPEGMetadata) inData;
249 if (jpegData.isStream) {
250 return inData;
251 }
252 }
253 return null;
254 }
255
256 @Override
257 public IIOMetadata
258 convertImageMetadata(IIOMetadata inData,
259 ImageTypeSpecifier imageType,
260 ImageWriteParam param) {
261 setThreadLock();
262 try {
263 return convertImageMetadataOnThread(inData, imageType, param);
264 } finally {
265 clearThreadLock();
266 }
267 }
268
269 private IIOMetadata
270 convertImageMetadataOnThread(IIOMetadata inData,
271 ImageTypeSpecifier imageType,
272 ImageWriteParam param) {
273 // If it's one of ours, just return it
274 if (inData instanceof JPEGMetadata) {
275 JPEGMetadata jpegData = (JPEGMetadata) inData;
276 if (!jpegData.isStream) {
288 IIOMetadataFormatImpl.standardMetadataFormatName;
289 Node tree = inData.getAsTree(formatName);
290 if (tree != null) {
291 JPEGMetadata jpegData = new JPEGMetadata(imageType,
292 param,
293 this);
294 try {
295 jpegData.setFromTree(formatName, tree);
296 } catch (IIOInvalidTreeException e) {
297 // Other plug-in generates bogus standard tree
298 // XXX Maybe this should put out a warning?
299 return null;
300 }
301
302 return jpegData;
303 }
304 }
305 return null;
306 }
307
308 @Override
309 public int getNumThumbnailsSupported(ImageTypeSpecifier imageType,
310 ImageWriteParam param,
311 IIOMetadata streamMetadata,
312 IIOMetadata imageMetadata) {
313 // Check whether sufficient data is available.
314 if (imageType == null && imageMetadata == null) {
315 // The method has been invoked with insufficient data. Henceforth
316 // we return -1 as recommended by ImageWriter specification.
317 return -1;
318 }
319
320 // Check if the image type and metadata are JFIF compatible.
321 if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
322 return Integer.MAX_VALUE;
323 }
324 return 0;
325 }
326
327 static final Dimension [] preferredThumbSizes = {new Dimension(1, 1),
328 new Dimension(255, 255)};
329 @Override
330 public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType,
331 ImageWriteParam param,
332 IIOMetadata streamMetadata,
333 IIOMetadata imageMetadata) {
334 if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
335 return preferredThumbSizes.clone();
336 }
337 return null;
338 }
339
340 private boolean jfifOK(ImageTypeSpecifier imageType,
341 ImageWriteParam param,
342 IIOMetadata streamMetadata,
343 IIOMetadata imageMetadata) {
344 // If the image type and metadata are JFIF compatible, return true
345 if ((imageType != null) &&
346 (!JPEG.isJFIFcompliant(imageType, true))) {
347 return false;
348 }
349 if (imageMetadata != null) {
350 JPEGMetadata metadata = null;
351 if (imageMetadata instanceof JPEGMetadata) {
352 metadata = (JPEGMetadata) imageMetadata;
353 } else {
354 metadata = (JPEGMetadata)convertImageMetadata(imageMetadata,
355 imageType,
356 param);
357 }
358 // metadata must have a jfif node
359 if (metadata.findMarkerSegment
360 (JFIFMarkerSegment.class, true) == null){
361 return false;
362 }
363 }
364 return true;
365 }
366
367 @Override
368 public boolean canWriteRasters() {
369 return true;
370 }
371
372 @Override
373 public void write(IIOMetadata streamMetadata,
374 IIOImage image,
375 ImageWriteParam param) throws IOException {
376 setThreadLock();
377 try {
378 cbLock.check();
379
380 writeOnThread(streamMetadata, image, param);
381 } finally {
382 clearThreadLock();
383 }
384 }
385
386 private void writeOnThread(IIOMetadata streamMetadata,
387 IIOImage image,
388 ImageWriteParam param) throws IOException {
389
390 if (ios == null) {
391 throw new IllegalStateException("Output has not been set!");
392 }
1041 cbLock.lock();
1042 try {
1043 if (aborted) {
1044 processWriteAborted();
1045 } else {
1046 processImageComplete();
1047 }
1048
1049 ios.flush();
1050 } finally {
1051 cbLock.unlock();
1052 }
1053 currentImage++; // After a successful write
1054 }
1055
1056 @Override
1057 public boolean canWriteSequence() {
1058 return true;
1059 }
1060
1061 @Override
1062 public void prepareWriteSequence(IIOMetadata streamMetadata)
1063 throws IOException {
1064 setThreadLock();
1065 try {
1066 cbLock.check();
1067
1068 prepareWriteSequenceOnThread(streamMetadata);
1069 } finally {
1070 clearThreadLock();
1071 }
1072 }
1073
1074 private void prepareWriteSequenceOnThread(IIOMetadata streamMetadata)
1075 throws IOException {
1076 if (ios == null) {
1077 throw new IllegalStateException("Output has not been set!");
1078 }
1079
1080 /*
1081 * from jpeg_metadata.html:
1123 streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
1124 }
1125 streamACHuffmanTables =
1126 collectHTablesFromMetadata(jmeta, false);
1127 if (streamACHuffmanTables == null) {
1128 streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
1129 }
1130
1131 // Now write them out
1132 writeTables(structPointer,
1133 streamQTables,
1134 streamDCHuffmanTables,
1135 streamACHuffmanTables);
1136 } else {
1137 throw new IIOException("Stream metadata must be JPEG metadata");
1138 }
1139 }
1140 sequencePrepared = true;
1141 }
1142
1143 @Override
1144 public void writeToSequence(IIOImage image, ImageWriteParam param)
1145 throws IOException {
1146 setThreadLock();
1147 try {
1148 cbLock.check();
1149
1150 if (sequencePrepared == false) {
1151 throw new IllegalStateException("sequencePrepared not called!");
1152 }
1153 // In the case of JPEG this does nothing different from write
1154 write(null, image, param);
1155 } finally {
1156 clearThreadLock();
1157 }
1158 }
1159
1160 @Override
1161 public void endWriteSequence() throws IOException {
1162 setThreadLock();
1163 try {
1164 cbLock.check();
1165
1166 if (sequencePrepared == false) {
1167 throw new IllegalStateException("sequencePrepared not called!");
1168 }
1169 sequencePrepared = false;
1170 } finally {
1171 clearThreadLock();
1172 }
1173 }
1174
1175 @Override
1176 public synchronized void abort() {
1177 setThreadLock();
1178 try {
1179 /**
1180 * NB: we do not check the call back lock here, we allow to abort
1181 * the reader any time.
1182 */
1183 super.abort();
1184 abortWrite(structPointer);
1185 } finally {
1186 clearThreadLock();
1187 }
1188 }
1189
1190 @Override
1191 protected synchronized void clearAbortRequest() {
1192 setThreadLock();
1193 try {
1194 cbLock.check();
1195 if (abortRequested()) {
1200 setDest(structPointer);
1201 }
1202 } finally {
1203 clearThreadLock();
1204 }
1205 }
1206
1207 private void resetInternalState() {
1208 // reset C structures
1209 resetWriter(structPointer);
1210
1211 // reset local Java structures
1212 srcRas = null;
1213 raster = null;
1214 convertTosRGB = false;
1215 currentImage = 0;
1216 numScans = 0;
1217 metadata = null;
1218 }
1219
1220 @Override
1221 public void reset() {
1222 setThreadLock();
1223 try {
1224 cbLock.check();
1225
1226 super.reset();
1227 } finally {
1228 clearThreadLock();
1229 }
1230 }
1231
1232 @Override
1233 public void dispose() {
1234 setThreadLock();
1235 try {
1236 cbLock.check();
1237
1238 if (structPointer != 0) {
1239 disposerRecord.dispose();
1240 structPointer = 0;
1241 }
1242 } finally {
1243 clearThreadLock();
1244 }
1245 }
1246
1247 ////////// End of public API
1248
1249 ///////// Package-access API
1250
1251 /**
1252 * Called by the native code or other classes to signal a warning.
1733 }
1734 }
1735 }
1736
1737 /** Aborts the current write in the native code */
1738 private native void abortWrite(long structPointer);
1739
1740 /** Resets native structures */
1741 private native void resetWriter(long structPointer);
1742
1743 /** Releases native structures */
1744 private static native void disposeWriter(long structPointer);
1745
1746 private static class JPEGWriterDisposerRecord implements DisposerRecord {
1747 private long pData;
1748
1749 public JPEGWriterDisposerRecord(long pData) {
1750 this.pData = pData;
1751 }
1752
1753 @Override
1754 public synchronized void dispose() {
1755 if (pData != 0) {
1756 disposeWriter(pData);
1757 pData = 0;
1758 }
1759 }
1760 }
1761
1762 /**
1763 * This method is called from native code in order to write encoder
1764 * output to the destination.
1765 *
1766 * We block any attempt to change the writer state during this
1767 * method, in order to prevent a corruption of the native encoder
1768 * state.
1769 */
1770 private void writeOutputData(byte[] data, int offset, int len)
1771 throws IOException
1772 {
1773 cbLock.lock();
|