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.util.Date;
023    
024    import org.apache.commons.compress.archivers.ArchiveEntry;
025    
026    /**
027     * Represents an archive entry in the "ar" format.
028     * 
029     * Each AR archive starts with "!<arch>" followed by a LF. After these 8 bytes
030     * the archive entries are listed. The format of an entry header is as it follows:
031     * 
032     * <pre>
033     * START BYTE   END BYTE    NAME                    FORMAT      LENGTH
034     * 0            15          File name               ASCII       16
035     * 16           27          Modification timestamp  Decimal     12
036     * 28           33          Owner ID                Decimal     6
037     * 34           39          Group ID                Decimal     6
038     * 40           47          File mode               Octal       8
039     * 48           57          File size (bytes)       Decimal     10
040     * 58           59          File magic              \140\012    2
041     * </pre>
042     * 
043     * This specifies that an ar archive entry header contains 60 bytes.
044     * 
045     * Due to the limitation of the file name length to 16 bytes GNU and BSD has
046     * their own variants of this format. This formats are currently not supported
047     * and file names with a bigger size than 16 bytes are not possible at the
048     * moment.
049     * 
050     * @Immutable
051     */
052    public class ArArchiveEntry implements ArchiveEntry {
053    
054        /** The header for each entry */
055        public static final String HEADER = "!<arch>\n";
056    
057        /** The trailer for each entry */
058        public static final String TRAILER = "`\012";
059    
060        /**
061         * SVR4/GNU adds a trailing / to names; BSD does not.
062         * They also vary in how names longer than 16 characters are represented.
063         * (Not yet supported by this implementation)
064         */
065        private final String name;
066        private final int userId;
067        private final int groupId;
068        private final int mode;
069        private static final int DEFAULT_MODE = 33188; // = (octal) 0100644 
070        private final long lastModified;
071        private final long length;
072    
073        /**
074         * Create a new instance using a couple of default values.
075         *
076         * <p>Sets userId and groupId to 0, the octal file mode to 644 and
077         * the last modified time to the current time.</p>
078         *
079         * @param name name of the entry
080         * @param length length of the entry in bytes
081         */
082        public ArArchiveEntry(String name, long length) {
083            this(name, length, 0, 0, DEFAULT_MODE,
084                 System.currentTimeMillis() / 1000);
085        }
086    
087        /**
088         * Create a new instance.
089         *
090         * @param name name of the entry
091         * @param length length of the entry in bytes
092         * @param userId numeric user id
093         * @param groupId numeric group id
094         * @param mode file mode
095         * @param lastModified last modified time in seconds since the epoch
096         */
097        public ArArchiveEntry(String name, long length, int userId, int groupId,
098                              int mode, long lastModified) {
099            this.name = name;
100            this.length = length;
101            this.userId = userId;
102            this.groupId = groupId;
103            this.mode = mode;
104            this.lastModified = lastModified;
105        }
106    
107        /**
108         * Create a new instance using the attributes of the given file
109         */
110        public ArArchiveEntry(File inputFile, String entryName) {
111            // TODO sort out mode
112            this(entryName, inputFile.isFile() ? inputFile.length() : 0,
113                 0, 0, DEFAULT_MODE, inputFile.lastModified() / 1000);
114        }
115    
116        /** {@inheritDoc} */
117        public long getSize() {
118            return this.getLength();
119        }
120    
121        /** {@inheritDoc} */
122        public String getName() {
123            return name;
124        }
125    
126        public int getUserId() {
127            return userId;
128        }
129    
130        public int getGroupId() {
131            return groupId;
132        }
133    
134        public int getMode() {
135            return mode;
136        }
137    
138        /**
139         * Last modified time in seconds since the epoch.
140         */
141        public long getLastModified() {
142            return lastModified;
143        }
144    
145        /** {@inheritDoc} */
146        public Date getLastModifiedDate() {
147            return new Date(1000 * getLastModified());
148        }
149    
150        public long getLength() {
151            return length;
152        }
153    
154        /** {@inheritDoc} */
155        public boolean isDirectory() {
156            return false;
157        }
158    
159        /** {@inheritDoc} */
160        public int hashCode() {
161            final int prime = 31;
162            int result = 1;
163            result = prime * result + ((name == null) ? 0 : name.hashCode());
164            return result;
165        }
166    
167        /** {@inheritDoc} */
168        public boolean equals(Object obj) {
169            if (this == obj) {
170                return true;
171            }
172            if (obj == null || getClass() != obj.getClass()) {
173                return false;
174            }
175            ArArchiveEntry other = (ArArchiveEntry) obj;
176            if (name == null) {
177                if (other.name != null) {
178                    return false;
179                }
180            } else if (!name.equals(other.name)) {
181                return false;
182            }
183            return true;
184        }
185    }