001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.zip;
020:
021: import java.io.File;
022: import java.io.FileOutputStream;
023: import java.io.FilterOutputStream;
024: import java.io.IOException;
025: import java.io.OutputStream;
026: import java.io.RandomAccessFile;
027: import java.io.UnsupportedEncodingException;
028: import java.util.Date;
029: import java.util.Hashtable;
030: import java.util.Vector;
031: import java.util.zip.CRC32;
032: import java.util.zip.Deflater;
033: import java.util.zip.ZipException;
034:
035: /**
036: * Reimplementation of {@link java.util.zip.ZipOutputStream
037: * java.util.zip.ZipOutputStream} that does handle the extended
038: * functionality of this package, especially internal/external file
039: * attributes and extra fields with different layouts for local file
040: * data and central directory entries.
041: *
042: * <p>This class will try to use {@link java.io.RandomAccessFile
043: * RandomAccessFile} when you know that the output is going to go to a
044: * file.</p>
045: *
046: * <p>If RandomAccessFile cannot be used, this implementation will use
047: * a Data Descriptor to store size and CRC information for {@link
048: * #DEFLATED DEFLATED} entries, this means, you don't need to
049: * calculate them yourself. Unfortunately this is not possible for
050: * the {@link #STORED STORED} method, here setting the CRC and
051: * uncompressed size information is required before {@link
052: * #putNextEntry putNextEntry} can be called.</p>
053: *
054: */
055: public class ZipOutputStream extends FilterOutputStream {
056:
057: /**
058: * Compression method for deflated entries.
059: *
060: * @since 1.1
061: */
062: public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
063:
064: /**
065: * Default compression level for deflated entries.
066: *
067: * @since Ant 1.7
068: */
069: public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
070:
071: /**
072: * Compression method for stored entries.
073: *
074: * @since 1.1
075: */
076: public static final int STORED = java.util.zip.ZipEntry.STORED;
077:
078: /**
079: * Current entry.
080: *
081: * @since 1.1
082: */
083: private ZipEntry entry;
084:
085: /**
086: * The file comment.
087: *
088: * @since 1.1
089: */
090: private String comment = "";
091:
092: /**
093: * Compression level for next entry.
094: *
095: * @since 1.1
096: */
097: private int level = DEFAULT_COMPRESSION;
098:
099: /**
100: * Has the compression level changed when compared to the last
101: * entry?
102: *
103: * @since 1.5
104: */
105: private boolean hasCompressionLevelChanged = false;
106:
107: /**
108: * Default compression method for next entry.
109: *
110: * @since 1.1
111: */
112: private int method = java.util.zip.ZipEntry.DEFLATED;
113:
114: /**
115: * List of ZipEntries written so far.
116: *
117: * @since 1.1
118: */
119: private Vector entries = new Vector();
120:
121: /**
122: * CRC instance to avoid parsing DEFLATED data twice.
123: *
124: * @since 1.1
125: */
126: private CRC32 crc = new CRC32();
127:
128: /**
129: * Count the bytes written to out.
130: *
131: * @since 1.1
132: */
133: private long written = 0;
134:
135: /**
136: * Data for local header data
137: *
138: * @since 1.1
139: */
140: private long dataStart = 0;
141:
142: /**
143: * Offset for CRC entry in the local file header data for the
144: * current entry starts here.
145: *
146: * @since 1.15
147: */
148: private long localDataStart = 0;
149:
150: /**
151: * Start of central directory.
152: *
153: * @since 1.1
154: */
155: private long cdOffset = 0;
156:
157: /**
158: * Length of central directory.
159: *
160: * @since 1.1
161: */
162: private long cdLength = 0;
163:
164: /**
165: * Helper, a 0 as ZipShort.
166: *
167: * @since 1.1
168: */
169: private static final byte[] ZERO = { 0, 0 };
170:
171: /**
172: * Helper, a 0 as ZipLong.
173: *
174: * @since 1.1
175: */
176: private static final byte[] LZERO = { 0, 0, 0, 0 };
177:
178: /**
179: * Holds the offsets of the LFH starts for each entry.
180: *
181: * @since 1.1
182: */
183: private Hashtable offsets = new Hashtable();
184:
185: /**
186: * The encoding to use for filenames and the file comment.
187: *
188: * <p>For a list of possible values see <a
189: * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
190: * Defaults to the platform's default character encoding.</p>
191: *
192: * @since 1.3
193: */
194: private String encoding = null;
195:
196: // CheckStyle:VisibilityModifier OFF - bc
197:
198: /**
199: * This Deflater object is used for output.
200: *
201: * <p>This attribute is only protected to provide a level of API
202: * backwards compatibility. This class used to extend {@link
203: * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
204: * Revision 1.13.</p>
205: *
206: * @since 1.14
207: */
208: protected Deflater def = new Deflater(level, true);
209:
210: /**
211: * This buffer servers as a Deflater.
212: *
213: * <p>This attribute is only protected to provide a level of API
214: * backwards compatibility. This class used to extend {@link
215: * java.util.zip.DeflaterOutputStream DeflaterOutputStream} up to
216: * Revision 1.13.</p>
217: *
218: * @since 1.14
219: */
220: protected byte[] buf = new byte[512];
221:
222: // CheckStyle:VisibilityModifier ON
223:
224: /**
225: * Optional random access output.
226: *
227: * @since 1.14
228: */
229: private RandomAccessFile raf = null;
230:
231: /**
232: * Creates a new ZIP OutputStream filtering the underlying stream.
233: * @param out the outputstream to zip
234: * @since 1.1
235: */
236: public ZipOutputStream(OutputStream out) {
237: super (out);
238: }
239:
240: /**
241: * Creates a new ZIP OutputStream writing to a File. Will use
242: * random access if possible.
243: * @param file the file to zip to
244: * @since 1.14
245: * @throws IOException on error
246: */
247: public ZipOutputStream(File file) throws IOException {
248: super (null);
249:
250: try {
251: raf = new RandomAccessFile(file, "rw");
252: raf.setLength(0);
253: } catch (IOException e) {
254: if (raf != null) {
255: try {
256: raf.close();
257: } catch (IOException inner) {
258: // ignore
259: }
260: raf = null;
261: }
262: out = new FileOutputStream(file);
263: }
264: }
265:
266: /**
267: * This method indicates whether this archive is writing to a seekable stream (i.e., to a random
268: * access file).
269: *
270: * <p>For seekable streams, you don't need to calculate the CRC or
271: * uncompressed size for {@link #STORED} entries before
272: * invoking {@link #putNextEntry}.
273: * @return true if seekable
274: * @since 1.17
275: */
276: public boolean isSeekable() {
277: return raf != null;
278: }
279:
280: /**
281: * The encoding to use for filenames and the file comment.
282: *
283: * <p>For a list of possible values see <a
284: * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
285: * Defaults to the platform's default character encoding.</p>
286: * @param encoding the encoding value
287: * @since 1.3
288: */
289: public void setEncoding(String encoding) {
290: this .encoding = encoding;
291: }
292:
293: /**
294: * The encoding to use for filenames and the file comment.
295: *
296: * @return null if using the platform's default character encoding.
297: *
298: * @since 1.3
299: */
300: public String getEncoding() {
301: return encoding;
302: }
303:
304: /**
305: * Finishs writing the contents and closes this as well as the
306: * underlying stream.
307: *
308: * @since 1.1
309: * @throws IOException on error
310: */
311: public void finish() throws IOException {
312: closeEntry();
313: cdOffset = written;
314: for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
315: writeCentralFileHeader((ZipEntry) entries.elementAt(i));
316: }
317: cdLength = written - cdOffset;
318: writeCentralDirectoryEnd();
319: offsets.clear();
320: entries.removeAllElements();
321: }
322:
323: /**
324: * Writes all necessary data for this entry.
325: *
326: * @since 1.1
327: * @throws IOException on error
328: */
329: public void closeEntry() throws IOException {
330: if (entry == null) {
331: return;
332: }
333:
334: long realCrc = crc.getValue();
335: crc.reset();
336:
337: if (entry.getMethod() == DEFLATED) {
338: def.finish();
339: while (!def.finished()) {
340: deflate();
341: }
342:
343: entry.setSize(adjustToLong(def.getTotalIn()));
344: entry.setCompressedSize(adjustToLong(def.getTotalOut()));
345: entry.setCrc(realCrc);
346:
347: def.reset();
348:
349: written += entry.getCompressedSize();
350: } else if (raf == null) {
351: if (entry.getCrc() != realCrc) {
352: throw new ZipException("bad CRC checksum for entry "
353: + entry.getName() + ": "
354: + Long.toHexString(entry.getCrc())
355: + " instead of " + Long.toHexString(realCrc));
356: }
357:
358: if (entry.getSize() != written - dataStart) {
359: throw new ZipException("bad size for entry "
360: + entry.getName() + ": " + entry.getSize()
361: + " instead of " + (written - dataStart));
362: }
363: } else { /* method is STORED and we used RandomAccessFile */
364: long size = written - dataStart;
365:
366: entry.setSize(size);
367: entry.setCompressedSize(size);
368: entry.setCrc(realCrc);
369: }
370:
371: // If random access output, write the local file header containing
372: // the correct CRC and compressed/uncompressed sizes
373: if (raf != null) {
374: long save = raf.getFilePointer();
375:
376: raf.seek(localDataStart);
377: writeOut(ZipLong.getBytes(entry.getCrc()));
378: writeOut(ZipLong.getBytes(entry.getCompressedSize()));
379: writeOut(ZipLong.getBytes(entry.getSize()));
380: raf.seek(save);
381: }
382:
383: writeDataDescriptor(entry);
384: entry = null;
385: }
386:
387: /**
388: * Begin writing next entry.
389: * @param ze the entry to write
390: * @since 1.1
391: * @throws IOException on error
392: */
393: public void putNextEntry(ZipEntry ze) throws IOException {
394: closeEntry();
395:
396: entry = ze;
397: entries.addElement(entry);
398:
399: if (entry.getMethod() == -1) { // not specified
400: entry.setMethod(method);
401: }
402:
403: if (entry.getTime() == -1) { // not specified
404: entry.setTime(System.currentTimeMillis());
405: }
406:
407: // Size/CRC not required if RandomAccessFile is used
408: if (entry.getMethod() == STORED && raf == null) {
409: if (entry.getSize() == -1) {
410: throw new ZipException(
411: "uncompressed size is required for"
412: + " STORED method when not writing to a"
413: + " file");
414: }
415: if (entry.getCrc() == -1) {
416: throw new ZipException(
417: "crc checksum is required for STORED"
418: + " method when not writing to a file");
419: }
420: entry.setCompressedSize(entry.getSize());
421: }
422:
423: if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
424: def.setLevel(level);
425: hasCompressionLevelChanged = false;
426: }
427: writeLocalFileHeader(entry);
428: }
429:
430: /**
431: * Set the file comment.
432: * @param comment the comment
433: * @since 1.1
434: */
435: public void setComment(String comment) {
436: this .comment = comment;
437: }
438:
439: /**
440: * Sets the compression level for subsequent entries.
441: *
442: * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
443: * @param level the compression level.
444: * @throws IllegalArgumentException if an invalid compression level is specified.
445: * @since 1.1
446: */
447: public void setLevel(int level) {
448: if (level < Deflater.DEFAULT_COMPRESSION
449: || level > Deflater.BEST_COMPRESSION) {
450: throw new IllegalArgumentException(
451: "Invalid compression level: " + level);
452: }
453: hasCompressionLevelChanged = (this .level != level);
454: this .level = level;
455: }
456:
457: /**
458: * Sets the default compression method for subsequent entries.
459: *
460: * <p>Default is DEFLATED.</p>
461: * @param method an <code>int</code> from java.util.zip.ZipEntry
462: * @since 1.1
463: */
464: public void setMethod(int method) {
465: this .method = method;
466: }
467:
468: /**
469: * Writes bytes to ZIP entry.
470: * @param b the byte array to write
471: * @param offset the start position to write from
472: * @param length the number of bytes to write
473: * @throws IOException on error
474: */
475: public void write(byte[] b, int offset, int length)
476: throws IOException {
477: if (entry.getMethod() == DEFLATED) {
478: if (length > 0) {
479: if (!def.finished()) {
480: def.setInput(b, offset, length);
481: while (!def.needsInput()) {
482: deflate();
483: }
484: }
485: }
486: } else {
487: writeOut(b, offset, length);
488: written += length;
489: }
490: crc.update(b, offset, length);
491: }
492:
493: /**
494: * Writes a single byte to ZIP entry.
495: *
496: * <p>Delegates to the three arg method.</p>
497: * @param b the byte to write
498: * @since 1.14
499: * @throws IOException on error
500: */
501: public void write(int b) throws IOException {
502: byte[] buff = new byte[1];
503: buff[0] = (byte) (b & 0xff);
504: write(buff, 0, 1);
505: }
506:
507: /**
508: * Closes this output stream and releases any system resources
509: * associated with the stream.
510: *
511: * @exception IOException if an I/O error occurs.
512: * @since 1.14
513: */
514: public void close() throws IOException {
515: finish();
516:
517: if (raf != null) {
518: raf.close();
519: }
520: if (out != null) {
521: out.close();
522: }
523: }
524:
525: /**
526: * Flushes this output stream and forces any buffered output bytes
527: * to be written out to the stream.
528: *
529: * @exception IOException if an I/O error occurs.
530: * @since 1.14
531: */
532: public void flush() throws IOException {
533: if (out != null) {
534: out.flush();
535: }
536: }
537:
538: /*
539: * Various ZIP constants
540: */
541: /**
542: * local file header signature
543: *
544: * @since 1.1
545: */
546: protected static final byte[] LFH_SIG = ZipLong
547: .getBytes(0X04034B50L);
548: /**
549: * data descriptor signature
550: *
551: * @since 1.1
552: */
553: protected static final byte[] DD_SIG = ZipLong
554: .getBytes(0X08074B50L);
555: /**
556: * central file header signature
557: *
558: * @since 1.1
559: */
560: protected static final byte[] CFH_SIG = ZipLong
561: .getBytes(0X02014B50L);
562: /**
563: * end of central dir signature
564: *
565: * @since 1.1
566: */
567: protected static final byte[] EOCD_SIG = ZipLong
568: .getBytes(0X06054B50L);
569:
570: /**
571: * Writes next block of compressed data to the output stream.
572: * @throws IOException on error
573: *
574: * @since 1.14
575: */
576: protected final void deflate() throws IOException {
577: int len = def.deflate(buf, 0, buf.length);
578: if (len > 0) {
579: writeOut(buf, 0, len);
580: }
581: }
582:
583: /**
584: * Writes the local file header entry
585: * @param ze the entry to write
586: * @throws IOException on error
587: *
588: * @since 1.1
589: */
590: protected void writeLocalFileHeader(ZipEntry ze) throws IOException {
591: offsets.put(ze, ZipLong.getBytes(written));
592:
593: writeOut(LFH_SIG);
594: written += 4;
595:
596: //store method in local variable to prevent multiple method calls
597: final int zipMethod = ze.getMethod();
598:
599: // version needed to extract
600: // general purpose bit flag
601: if (zipMethod == DEFLATED && raf == null) {
602: // requires version 2 as we are going to store length info
603: // in the data descriptor
604: writeOut(ZipShort.getBytes(20));
605:
606: // bit3 set to signal, we use a data descriptor
607: writeOut(ZipShort.getBytes(8));
608: } else {
609: writeOut(ZipShort.getBytes(10));
610: writeOut(ZERO);
611: }
612: written += 4;
613:
614: // compression method
615: writeOut(ZipShort.getBytes(zipMethod));
616: written += 2;
617:
618: // last mod. time and date
619: writeOut(toDosTime(ze.getTime()));
620: written += 4;
621:
622: // CRC
623: // compressed length
624: // uncompressed length
625: localDataStart = written;
626: if (zipMethod == DEFLATED || raf != null) {
627: writeOut(LZERO);
628: writeOut(LZERO);
629: writeOut(LZERO);
630: } else {
631: writeOut(ZipLong.getBytes(ze.getCrc()));
632: writeOut(ZipLong.getBytes(ze.getSize()));
633: writeOut(ZipLong.getBytes(ze.getSize()));
634: }
635: written += 12;
636:
637: // file name length
638: byte[] name = getBytes(ze.getName());
639: writeOut(ZipShort.getBytes(name.length));
640: written += 2;
641:
642: // extra field length
643: byte[] extra = ze.getLocalFileDataExtra();
644: writeOut(ZipShort.getBytes(extra.length));
645: written += 2;
646:
647: // file name
648: writeOut(name);
649: written += name.length;
650:
651: // extra field
652: writeOut(extra);
653: written += extra.length;
654:
655: dataStart = written;
656: }
657:
658: /**
659: * Writes the data descriptor entry.
660: * @param ze the entry to write
661: * @throws IOException on error
662: *
663: * @since 1.1
664: */
665: protected void writeDataDescriptor(ZipEntry ze) throws IOException {
666: if (ze.getMethod() != DEFLATED || raf != null) {
667: return;
668: }
669: writeOut(DD_SIG);
670: writeOut(ZipLong.getBytes(entry.getCrc()));
671: writeOut(ZipLong.getBytes(entry.getCompressedSize()));
672: writeOut(ZipLong.getBytes(entry.getSize()));
673: written += 16;
674: }
675:
676: /**
677: * Writes the central file header entry.
678: * @param ze the entry to write
679: * @throws IOException on error
680: *
681: * @since 1.1
682: */
683: protected void writeCentralFileHeader(ZipEntry ze)
684: throws IOException {
685: writeOut(CFH_SIG);
686: written += 4;
687:
688: // version made by
689: writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
690: written += 2;
691:
692: // version needed to extract
693: // general purpose bit flag
694: if (ze.getMethod() == DEFLATED && raf == null) {
695: // requires version 2 as we are going to store length info
696: // in the data descriptor
697: writeOut(ZipShort.getBytes(20));
698:
699: // bit3 set to signal, we use a data descriptor
700: writeOut(ZipShort.getBytes(8));
701: } else {
702: writeOut(ZipShort.getBytes(10));
703: writeOut(ZERO);
704: }
705: written += 4;
706:
707: // compression method
708: writeOut(ZipShort.getBytes(ze.getMethod()));
709: written += 2;
710:
711: // last mod. time and date
712: writeOut(toDosTime(ze.getTime()));
713: written += 4;
714:
715: // CRC
716: // compressed length
717: // uncompressed length
718: writeOut(ZipLong.getBytes(ze.getCrc()));
719: writeOut(ZipLong.getBytes(ze.getCompressedSize()));
720: writeOut(ZipLong.getBytes(ze.getSize()));
721: written += 12;
722:
723: // file name length
724: byte[] name = getBytes(ze.getName());
725: writeOut(ZipShort.getBytes(name.length));
726: written += 2;
727:
728: // extra field length
729: byte[] extra = ze.getCentralDirectoryExtra();
730: writeOut(ZipShort.getBytes(extra.length));
731: written += 2;
732:
733: // file comment length
734: String comm = ze.getComment();
735: if (comm == null) {
736: comm = "";
737: }
738: byte[] commentB = getBytes(comm);
739: writeOut(ZipShort.getBytes(commentB.length));
740: written += 2;
741:
742: // disk number start
743: writeOut(ZERO);
744: written += 2;
745:
746: // internal file attributes
747: writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
748: written += 2;
749:
750: // external file attributes
751: writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
752: written += 4;
753:
754: // relative offset of LFH
755: writeOut((byte[]) offsets.get(ze));
756: written += 4;
757:
758: // file name
759: writeOut(name);
760: written += name.length;
761:
762: // extra field
763: writeOut(extra);
764: written += extra.length;
765:
766: // file comment
767: writeOut(commentB);
768: written += commentB.length;
769: }
770:
771: /**
772: * Writes the "End of central dir record".
773: * @throws IOException on error
774: *
775: * @since 1.1
776: */
777: protected void writeCentralDirectoryEnd() throws IOException {
778: writeOut(EOCD_SIG);
779:
780: // disk numbers
781: writeOut(ZERO);
782: writeOut(ZERO);
783:
784: // number of entries
785: byte[] num = ZipShort.getBytes(entries.size());
786: writeOut(num);
787: writeOut(num);
788:
789: // length and location of CD
790: writeOut(ZipLong.getBytes(cdLength));
791: writeOut(ZipLong.getBytes(cdOffset));
792:
793: // ZIP file comment
794: byte[] data = getBytes(comment);
795: writeOut(ZipShort.getBytes(data.length));
796: writeOut(data);
797: }
798:
799: /**
800: * Smallest date/time ZIP can handle.
801: *
802: * @since 1.1
803: */
804: private static final byte[] DOS_TIME_MIN = ZipLong
805: .getBytes(0x00002100L);
806:
807: /**
808: * Convert a Date object to a DOS date/time field.
809: * @param time the <code>Date</code> to convert
810: * @return the date as a <code>ZipLong</code>
811: * @since 1.1
812: */
813: protected static ZipLong toDosTime(Date time) {
814: return new ZipLong(toDosTime(time.getTime()));
815: }
816:
817: /**
818: * Convert a Date object to a DOS date/time field.
819: *
820: * <p>Stolen from InfoZip's <code>fileio.c</code></p>
821: * @param t number of milliseconds since the epoch
822: * @return the date as a byte array
823: * @since 1.26
824: */
825: protected static byte[] toDosTime(long t) {
826: Date time = new Date(t);
827: int year = time.getYear() + 1900;
828: if (year < 1980) {
829: return DOS_TIME_MIN;
830: }
831: int month = time.getMonth() + 1;
832: long value = ((year - 1980) << 25) | (month << 21)
833: | (time.getDate() << 16) | (time.getHours() << 11)
834: | (time.getMinutes() << 5) | (time.getSeconds() >> 1);
835: return ZipLong.getBytes(value);
836: }
837:
838: /**
839: * Retrieve the bytes for the given String in the encoding set for
840: * this Stream.
841: * @param name the string to get bytes from
842: * @return the bytes as a byte array
843: * @throws ZipException on error
844: *
845: * @since 1.3
846: */
847: protected byte[] getBytes(String name) throws ZipException {
848: if (encoding == null) {
849: return name.getBytes();
850: } else {
851: try {
852: return name.getBytes(encoding);
853: } catch (UnsupportedEncodingException uee) {
854: throw new ZipException(uee.getMessage());
855: }
856: }
857: }
858:
859: /**
860: * Write bytes to output or random access file.
861: * @param data the byte array to write
862: * @throws IOException on error
863: *
864: * @since 1.14
865: */
866: protected final void writeOut(byte[] data) throws IOException {
867: writeOut(data, 0, data.length);
868: }
869:
870: /**
871: * Write bytes to output or random access file.
872: * @param data the byte array to write
873: * @param offset the start position to write from
874: * @param length the number of bytes to write
875: * @throws IOException on error
876: *
877: * @since 1.14
878: */
879: protected final void writeOut(byte[] data, int offset, int length)
880: throws IOException {
881: if (raf != null) {
882: raf.write(data, offset, length);
883: } else {
884: out.write(data, offset, length);
885: }
886: }
887:
888: /**
889: * Assumes a negative integer really is a positive integer that
890: * has wrapped around and re-creates the original value.
891: * @param i the value to treat as unsigned int.
892: * @return the unsigned int as a long.
893: * @since 1.34
894: */
895: protected static long adjustToLong(int i) {
896: if (i < 0) {
897: return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
898: } else {
899: return i;
900: }
901: }
902:
903: }
|