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
046     * BSD has their own variants of this format. Currently Commons
047     * Compress can read but not write the GNU variant.  It fully supports
048     * the BSD variant.
049     * 
050     * @see <a href="http://www.freebsd.org/cgi/man.cgi?query=ar&sektion=5">ar man page</a>
051     *
052     * @Immutable
053     */
054    public class ArArchiveEntry implements ArchiveEntry {
055    
056        /** The header for each entry */
057        public static final String HEADER = "!<arch>\n";
058    
059        /** The trailer for each entry */
060        public static final String TRAILER = "`\012";
061    
062        /**
063         * SVR4/GNU adds a trailing / to names; BSD does not.
064         * They also vary in how names longer than 16 characters are represented.
065         * (Not yet fully supported by this implementation)
066         */
067        private final String name;
068        private final int userId;
069        private final int groupId;
070        private final int mode;
071        private static final int DEFAULT_MODE = 33188; // = (octal) 0100644 
072        private final long lastModified;
073        private final long length;
074    
075        /**
076         * Create a new instance using a couple of default values.
077         *
078         * <p>Sets userId and groupId to 0, the octal file mode to 644 and
079         * the last modified time to the current time.</p>
080         *
081         * @param name name of the entry
082         * @param length length of the entry in bytes
083         */
084        public ArArchiveEntry(String name, long length) {
085            this(name, length, 0, 0, DEFAULT_MODE,
086                 System.currentTimeMillis() / 1000);
087        }
088    
089        /**
090         * Create a new instance.
091         *
092         * @param name name of the entry
093         * @param length length of the entry in bytes
094         * @param userId numeric user id
095         * @param groupId numeric group id
096         * @param mode file mode
097         * @param lastModified last modified time in seconds since the epoch
098         */
099        public ArArchiveEntry(String name, long length, int userId, int groupId,
100                              int mode, long lastModified) {
101            this.name = name;
102            this.length = length;
103            this.userId = userId;
104            this.groupId = groupId;
105            this.mode = mode;
106            this.lastModified = lastModified;
107        }
108    
109        /**
110         * Create a new instance using the attributes of the given file
111         */
112        public ArArchiveEntry(File inputFile, String entryName) {
113            // TODO sort out mode
114            this(entryName, inputFile.isFile() ? inputFile.length() : 0,
115                 0, 0, DEFAULT_MODE, inputFile.lastModified() / 1000);
116        }
117    
118        /** {@inheritDoc} */
119        public long getSize() {
120            return this.getLength();
121        }
122    
123        /** {@inheritDoc} */
124        public String getName() {
125            return name;
126        }
127    
128        public int getUserId() {
129            return userId;
130        }
131    
132        public int getGroupId() {
133            return groupId;
134        }
135    
136        public int getMode() {
137            return mode;
138        }
139    
140        /**
141         * Last modified time in seconds since the epoch.
142         */
143        public long getLastModified() {
144            return lastModified;
145        }
146    
147        /** {@inheritDoc} */
148        public Date getLastModifiedDate() {
149            return new Date(1000 * getLastModified());
150        }
151    
152        public long getLength() {
153            return length;
154        }
155    
156        /** {@inheritDoc} */
157        public boolean isDirectory() {
158            return false;
159        }
160    
161        /** {@inheritDoc} */
162        @Override
163        public int hashCode() {
164            final int prime = 31;
165            int result = 1;
166            result = prime * result + ((name == null) ? 0 : name.hashCode());
167            return result;
168        }
169    
170        /** {@inheritDoc} */
171        @Override
172        public boolean equals(Object obj) {
173            if (this == obj) {
174                return true;
175            }
176            if (obj == null || getClass() != obj.getClass()) {
177                return false;
178            }
179            ArArchiveEntry other = (ArArchiveEntry) obj;
180            if (name == null) {
181                if (other.name != null) {
182                    return false;
183                }
184            } else if (!name.equals(other.name)) {
185                return false;
186            }
187            return true;
188        }
189    }