comparison lib/mod/src/de/masters_of_disaster/ant/tasks/ar/ArOutputStream.java @ 33:3d86f0391168

Work on improving the build system.
author David Barts <davidb@stashtea.com>
date Fri, 24 Apr 2020 19:45:57 -0700
parents
children
comparison
equal deleted inserted replaced
32:c06edc56669b 33:3d86f0391168
1 package de.masters_of_disaster.ant.tasks.ar;
2
3 import java.io.FilterOutputStream;
4 import java.io.OutputStream;
5 import java.io.IOException;
6
7 /**
8 * The ArOutputStream writes an ar archive as an OutputStream.
9 * Methods are provided to put entries, and then write their contents
10 * by writing to this stream using write().
11 */
12 public class ArOutputStream extends FilterOutputStream {
13 /** Fail if a long file name is required in the archive or the name contains spaces. */
14 public static final int LONGFILE_ERROR = 0;
15
16 /** Long paths will be truncated in the archive. Spaces are replaced by '_' */
17 public static final int LONGFILE_TRUNCATE = 1;
18
19 /** GNU ar variant is used to store long file names and file names with spaced in the archive. */
20 public static final int LONGFILE_GNU = 2;
21
22 /** BSD ar variant is used to store long file names and file names with spaced in the archive. */
23 public static final int LONGFILE_BSD = 3;
24
25 protected int currSize;
26 protected int currBytes;
27 protected byte[] oneBuf;
28 protected int longFileMode = LONGFILE_ERROR;
29 protected boolean writingStarted = false;
30 protected boolean inEntry = false;
31
32 public ArOutputStream(OutputStream os) throws IOException {
33 super(os);
34 if (null == os) {
35 throw new NullPointerException("os must not be null");
36 }
37 this.out.write(ArConstants.ARMAGIC,0,ArConstants.ARMAGIC.length);
38 this.oneBuf = new byte[1];
39 }
40
41 public void setLongFileMode(int longFileMode) {
42 if (writingStarted) {
43 throw new IllegalStateException("longFileMode cannot be changed after writing to the archive has begun");
44 }
45 if (LONGFILE_GNU == longFileMode) {
46 throw new UnsupportedOperationException("GNU variant isn't implemented yet");
47 }
48 if (LONGFILE_BSD == longFileMode) {
49 throw new UnsupportedOperationException("BSD variant isn't implemented yet");
50 }
51 this.longFileMode = longFileMode;
52 }
53
54 /**
55 * Put an entry on the output stream. This writes the entry's
56 * header record and positions the output stream for writing
57 * the contents of the entry. Once this method is called, the
58 * stream is ready for calls to write() to write the entry's
59 * contents. Once the contents are written, closeEntry()
60 * <B>MUST</B> be called to ensure that all buffered data
61 * is completely written to the output stream.
62 *
63 * @param entry The ArEntry to be written to the archive.
64 */
65 public void putNextEntry(ArEntry entry) throws IOException {
66 writingStarted = true;
67 if (inEntry) {
68 throw new IOException("the current entry has to be closed before starting a new one");
69 }
70 String filename = entry.getFilename();
71 if ((filename.length() >= ArConstants.NAMELEN)
72 && (longFileMode != LONGFILE_TRUNCATE)) {
73 throw new RuntimeException("file name \"" + entry.getFilename()
74 + "\" is too long ( > "
75 + ArConstants.NAMELEN + " bytes )");
76 }
77 if (-1 != filename.indexOf(' ')) {
78 if (longFileMode == LONGFILE_TRUNCATE) {
79 entry.setFilename(filename.replace(' ','_'));
80 } else {
81 throw new RuntimeException("file name \"" + entry.getFilename()
82 + "\" contains spaces");
83 }
84 }
85
86 byte[] headerBuf = new byte[ArConstants.HEADERLENGTH];
87 entry.writeEntryHeader(headerBuf);
88 this.out.write(headerBuf,0,ArConstants.HEADERLENGTH);
89
90 this.currBytes = 0;
91 this.currSize = (int) entry.getSize();
92 inEntry = true;
93 }
94
95 /**
96 * Close an entry. This method MUST be called for all file
97 * entries that contain data. The reason is that we must
98 * pad an entries data if it is of odd size.
99 */
100 public void closeEntry() throws IOException {
101 if (!inEntry) {
102 throw new IOException("we are not in an entry currently");
103 }
104
105 if (this.currBytes < this.currSize) {
106 throw new IOException("entry closed at '" + this.currBytes
107 + "' before the '" + this.currSize
108 + "' bytes specified in the header were written");
109 }
110
111 if (1 == (this.currSize & 1)) {
112 this.out.write(ArConstants.PADDING,0,1);
113 }
114
115 inEntry = false;
116 }
117
118 /**
119 * Writes a byte to the current ar archive entry.
120 *
121 * This method simply calls write( byte[], int, int ).
122 *
123 * @param b The byte to write to the archive.
124 */
125 public void write(int b) throws IOException {
126 this.oneBuf[0] = (byte) b;
127 this.write(this.oneBuf, 0, 1);
128 }
129
130 /**
131 * Writes bytes to the current ar archive entry.
132 *
133 * This method simply calls write( byte[], int, int ).
134 *
135 * @param wBuf The buffer to write to the archive.
136 */
137 public void write(byte[] wBuf) throws IOException {
138 this.write(wBuf, 0, wBuf.length);
139 }
140
141 /**
142 * Writes bytes to the current ar archive entry. This method
143 * is aware of the current entry and will throw an exception if
144 * you attempt to write bytes past the length specified for the
145 * current entry.
146 *
147 * @param wBuf The buffer to write to the archive.
148 * @param wOffset The offset in the buffer from which to get bytes.
149 * @param numToWrite The number of bytes to write.
150 */
151 public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
152 if (!inEntry) {
153 throw new IOException("we are not in an entry currently");
154 }
155
156 if ((this.currBytes + numToWrite) > this.currSize) {
157 throw new IOException("request to write '" + numToWrite
158 + "' bytes exceeds size in header of '"
159 + this.currSize + "' bytes");
160 }
161
162 if (numToWrite > 0) {
163 this.out.write(wBuf,wOffset,numToWrite);
164 this.currBytes += numToWrite;
165 }
166 }
167 }