001    /*
002     * $Id: ArchivEntry.java,v 1.7 2012/01/16 21:58:11 oboehm Exp $
003     *
004     * Copyright (c) 2008 by Oliver Boehm
005     *
006     * Licensed under the Apache License, Version 2.0 (the "License");
007     * you may not use this file except in compliance with the License.
008     * 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, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     *
018     * (c)reated 11-May-2009 by oliver (ob@oasd.de)
019     */
020    package patterntesting.runtime.util;
021    
022    import java.io.*;
023    import java.net.*;
024    import java.util.Arrays;
025    import java.util.zip.*;
026    
027    import org.apache.commons.io.*;
028    import org.slf4j.*;
029    
030    /**
031     * Unfortunately we can't extends URI because this is a final class.
032     * So now it is more or less implemented as URI wrapper and is intended for
033     * the use with zip and jar files to describe an entry inside an archive.
034     * <br/>
035     * Historically some parts of this class were developed for a log browser
036     * for Log4J. The facility to read (compressed) tar files (using
037     * org.apache.commons.compress.tar.*) were removed because we use it here only
038     * for zip and jar files.
039     *
040     * @author oliver (ob@aosd.de)
041     * @since 20.09.2007
042     * @version $Revision: 1.7 $
043     */
044    public class ArchivEntry {
045    
046        private static final Logger log = LoggerFactory.getLogger(ArchivEntry.class);
047        private File archiv = null;
048        private String entry = null;
049        private URI uri;
050        private Long size = null;
051    
052        /**
053         * Instantiates a new archiv entry.
054         *
055         * @param file the file
056         */
057        protected ArchivEntry(final File file) {
058            this.archiv = file;
059            try {
060                archiv = archiv.getCanonicalFile();
061            } catch (IOException ioe) {
062                log.info(ioe + " ignored - using absolute file for " + archiv);
063                archiv = archiv.getAbsoluteFile();
064            }
065            uri = this.archiv.toURI();
066        }
067    
068        /**
069         * Instantiates a new archiv entry.
070         *
071         * @param s the s
072         */
073        public ArchivEntry(final String s) {
074            try {
075                this.uri = new URI(s);
076                if (uri.getScheme() == null) {
077                    throw new IllegalArgumentException('"' + s + "\" is not a valid URI (scheme missing)");
078                }
079                initArchiv();
080            } catch (URISyntaxException e) {
081                throw new IllegalArgumentException('"' + s + "\" is not a valid URI", e);
082            }
083        }
084    
085        /**
086         * Instantiates a new archiv entry.
087         *
088         * @param uri the uri
089         */
090        public ArchivEntry(final URI uri) {
091            this.uri = uri;
092            initArchiv();
093        }
094    
095        /**
096         * Instantiates a new archiv entry.
097         *
098         * @param url the url
099         */
100        public ArchivEntry(final URL url) {
101            this.uri = Converter.toURI(url);
102            initArchiv();
103        }
104    
105        /**
106         * Instantiates a new archiv entry.
107         *
108         * @param scheme the scheme
109         * @param archive the archive
110         * @param entry the entry
111         *
112         * @throws URISyntaxException the URI syntax exception
113         */
114        public ArchivEntry(final String scheme, final File archive, final String entry) throws URISyntaxException {
115            this(archive);
116            uri = toURI(scheme, entry);
117        }
118    
119        private void initArchiv() {
120            assert uri.getScheme() != null;
121            if (uri.getScheme().equalsIgnoreCase("file")) {
122                this.archiv = new File(uri);
123                this.entry = null;
124            } else {
125                String name = uri.toString().substring(4);
126                int n = name.lastIndexOf("!");
127                try {
128                                    URI fileURI = new URI(name.substring(0, n));
129                                    this.archiv = Converter.toFile(fileURI);
130                            } catch (URISyntaxException e) {
131                                    this.archiv = new File(name.substring(5, n));
132                            }
133                this.entry = name.substring(n+1);
134                if (this.entry.charAt(0) == '/') {
135                    this.entry = this.entry.substring(1);
136                }
137            }
138        }
139    
140        /**
141         * To uri.
142         *
143         * @return the uRI
144         */
145        public URI toURI() {
146            return this.uri;
147        }
148    
149        private URI toURI(final String scheme, final String name) throws URISyntaxException {
150            String schemeURI = scheme + ":" + archiv.toURI().toString() + "!" + name;
151            return new URI(schemeURI).normalize();
152        }
153    
154        /**
155         * Checks if is file.
156         *
157         * @return true, if is file
158         */
159        public boolean isFile() {
160            return this.entry == null;
161        }
162    
163        /**
164         * Gets the file.
165         *
166         * @return the file
167         */
168        public File getFile() {
169            return this.archiv;
170        }
171    
172        /**
173         * Gets the zip file.
174         *
175         * @return the zip file
176         *
177         * @throws IOException Signals that an I/O exception has occurred.
178         */
179        public ZipFile getZipFile() throws IOException {
180            try {
181                return new ZipFile(this.archiv);
182            } catch (IOException ioe) {
183                IOException betterException = new IOException("can't get zip file \"" + this.archiv + '"');
184                betterException.initCause(ioe);
185                throw betterException;
186            }
187        }
188    
189        /**
190         * Gets the entry.
191         *
192         * @return the entry
193         */
194        public String getEntry() {
195            return this.entry;
196        }
197    
198        /**
199         * Gets the zip entry.
200         *
201         * @return the zip entry
202         */
203        public ZipEntry getZipEntry() {
204            return new ZipEntry(this.entry);
205        }
206    
207        /**
208         * Gets the size.
209         *
210         * @return the size
211         *
212         * @throws IOException Signals that an I/O exception has occurred.
213         */
214        public long getSize() throws IOException {
215            if (size == null) {
216                if (isFile()) {
217                    size = this.archiv.length();
218                } else {
219                    ZipFile zipFile = getZipFile();
220                    ZipEntry zipEntry = zipFile.getEntry(this.entry);
221                    size = zipEntry.getSize();
222                }
223            }
224            return size;
225        }
226    
227        /**
228         * Gets the bytes.
229         *
230         * @return the bytes
231         *
232         * @throws IOException Signals that an I/O exception has occurred.
233         */
234        public byte[] getBytes() throws IOException {
235            if (isFile()) {
236                return FileUtils.readFileToByteArray(this.archiv);
237            } else {
238                ZipFile zipFile = getZipFile();
239                ZipEntry zipEntry = zipFile.getEntry(this.entry);
240                InputStream istream = zipFile.getInputStream(zipEntry);
241                byte[] bytes = IOUtils.toByteArray(istream);
242                istream.close();
243                return bytes;
244            }
245        }
246    
247        /**
248         * Equals.
249         *
250         * @param other the other
251         * @return true, if successful
252         * @see java.lang.Object#equals(java.lang.Object)
253         */
254        @Override
255        public boolean equals(final Object other) {
256            if (other == null) {
257                return false;
258            }
259            try {
260                return equals((ArchivEntry) other);
261            } catch (ClassCastException e) {
262                return false;
263            }
264        }
265    
266        /**
267         * If two entries have a different size or not the same byte code they are
268         * considered as not equal.
269         *
270         * @param other the other
271         *
272         * @return true if they have the same size and the same byte code.
273         */
274        public boolean equals(final ArchivEntry other) {
275            try {
276                if (this.getSize() != other.getSize()) {
277                    return false;
278                }
279                return Arrays.equals(this.getBytes(), other.getBytes());
280            } catch (IOException ioe) {
281                log.debug("can't compare " + this + " with " + other, ioe);
282                return false;
283            }
284        }
285    
286        /**
287         * Hash code.
288         *
289         * @return the hash code
290         * @see java.lang.Object#hashCode()
291         */
292        @Override
293        public int hashCode() {
294            if (this.size == null) {
295                return 0;
296            } else {
297                return this.size.hashCode();
298            }
299        }
300    
301        /**
302         * To string.
303         *
304         * @return the string
305         * @see java.lang.Object#toString()
306         */
307        @Override
308        public String toString() {
309            return "ArchivEntry " + this.uri;
310        }
311    
312    }