001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.commons.compress.archivers.ar;
020
021 import java.io.File;
022 import java.io.IOException;
023 import java.io.OutputStream;
024
025 import org.apache.commons.compress.archivers.ArchiveEntry;
026 import org.apache.commons.compress.archivers.ArchiveOutputStream;
027 import org.apache.commons.compress.utils.ArchiveUtils;
028
029 /**
030 * Implements the "ar" archive format as an output stream.
031 *
032 * @NotThreadSafe
033 */
034 public class ArArchiveOutputStream extends ArchiveOutputStream {
035
036 private final OutputStream out;
037 private long archiveOffset = 0;
038 private long entryOffset = 0;
039 private ArArchiveEntry prevEntry;
040 private boolean haveUnclosedEntry = false;
041
042 /** indicates if this archive is finished */
043 private boolean finished = false;
044
045 public ArArchiveOutputStream( final OutputStream pOut ) {
046 this.out = pOut;
047 }
048
049 private long writeArchiveHeader() throws IOException {
050 byte [] header = ArchiveUtils.toAsciiBytes(ArArchiveEntry.HEADER);
051 out.write(header);
052 return header.length;
053 }
054
055 public void closeArchiveEntry() throws IOException {
056 if(finished) {
057 throw new IOException("Stream has already been finished");
058 }
059 if (prevEntry == null || !haveUnclosedEntry){
060 throw new IOException("No current entry to close");
061 }
062 if ((entryOffset % 2) != 0) {
063 out.write('\n'); // Pad byte
064 archiveOffset++;
065 }
066 haveUnclosedEntry = false;
067 }
068
069 public void putArchiveEntry( final ArchiveEntry pEntry ) throws IOException {
070 if(finished) {
071 throw new IOException("Stream has already been finished");
072 }
073
074 ArArchiveEntry pArEntry = (ArArchiveEntry)pEntry;
075 if (prevEntry == null) {
076 archiveOffset += writeArchiveHeader();
077 } else {
078 if (prevEntry.getLength() != entryOffset) {
079 throw new IOException("length does not match entry (" + prevEntry.getLength() + " != " + entryOffset);
080 }
081
082 if (haveUnclosedEntry) {
083 closeArchiveEntry();
084 }
085 }
086
087 prevEntry = pArEntry;
088
089 archiveOffset += writeEntryHeader(pArEntry);
090
091 entryOffset = 0;
092 haveUnclosedEntry = true;
093 }
094
095 private long fill( final long pOffset, final long pNewOffset, final char pFill ) throws IOException {
096 final long diff = pNewOffset - pOffset;
097
098 if (diff > 0) {
099 for (int i = 0; i < diff; i++) {
100 write(pFill);
101 }
102 }
103
104 return pNewOffset;
105 }
106
107 private long write( final String data ) throws IOException {
108 final byte[] bytes = data.getBytes("ascii");
109 write(bytes);
110 return bytes.length;
111 }
112
113 private long writeEntryHeader( final ArArchiveEntry pEntry ) throws IOException {
114
115 long offset = 0;
116
117 final String n = pEntry.getName();
118 if (n.length() > 16) {
119 throw new IOException("filename too long, > 16 chars: "+n);
120 }
121 offset += write(n);
122
123 offset = fill(offset, 16, ' ');
124 final String m = "" + (pEntry.getLastModified() / 1000);
125 if (m.length() > 12) {
126 throw new IOException("modified too long");
127 }
128 offset += write(m);
129
130 offset = fill(offset, 28, ' ');
131 final String u = "" + pEntry.getUserId();
132 if (u.length() > 6) {
133 throw new IOException("userid too long");
134 }
135 offset += write(u);
136
137 offset = fill(offset, 34, ' ');
138 final String g = "" + pEntry.getGroupId();
139 if (g.length() > 6) {
140 throw new IOException("groupid too long");
141 }
142 offset += write(g);
143
144 offset = fill(offset, 40, ' ');
145 final String fm = "" + Integer.toString(pEntry.getMode(), 8);
146 if (fm.length() > 8) {
147 throw new IOException("filemode too long");
148 }
149 offset += write(fm);
150
151 offset = fill(offset, 48, ' ');
152 final String s = "" + pEntry.getLength();
153 if (s.length() > 10) {
154 throw new IOException("size too long");
155 }
156 offset += write(s);
157
158 offset = fill(offset, 58, ' ');
159
160 offset += write(ArArchiveEntry.TRAILER);
161
162 return offset;
163 }
164
165 public void write(byte[] b, int off, int len) throws IOException {
166 out.write(b, off, len);
167 count(len);
168 entryOffset += len;
169 }
170
171 public void close() throws IOException {
172 if(!finished) {
173 finish();
174 }
175 out.close();
176 prevEntry = null;
177 }
178
179 public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
180 throws IOException {
181 if(finished) {
182 throw new IOException("Stream has already been finished");
183 }
184 return new ArArchiveEntry(inputFile, entryName);
185 }
186
187 /* (non-Javadoc)
188 * @see org.apache.commons.compress.archivers.ArchiveOutputStream#finish()
189 */
190 public void finish() throws IOException {
191 if(haveUnclosedEntry) {
192 throw new IOException("This archive contains unclosed entries.");
193 } else if(finished) {
194 throw new IOException("This archive has already been finished");
195 }
196 finished = true;
197 }
198 }