001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.vfs2.provider.zip;
018
019import java.io.InputStream;
020import java.util.HashSet;
021import java.util.zip.ZipEntry;
022
023import org.apache.commons.vfs2.FileName;
024import org.apache.commons.vfs2.FileSystemException;
025import org.apache.commons.vfs2.FileType;
026import org.apache.commons.vfs2.provider.AbstractFileName;
027import org.apache.commons.vfs2.provider.AbstractFileObject;
028
029/**
030 * A file in a ZIP file system.
031 */
032public class ZipFileObject extends AbstractFileObject<ZipFileSystem> {
033    /** The ZipEntry. */
034    protected ZipEntry entry;
035    private final HashSet<String> children = new HashSet<>();
036    private FileType type;
037
038    protected ZipFileObject(final AbstractFileName name, final ZipEntry entry, final ZipFileSystem fs,
039            final boolean zipExists) throws FileSystemException {
040        super(name, fs);
041        setZipEntry(entry);
042        if (!zipExists) {
043            type = FileType.IMAGINARY;
044        }
045    }
046
047    /**
048     * Sets the details for this file object.
049     *
050     * @param entry ZIP information related to this file.
051     */
052    protected void setZipEntry(final ZipEntry entry) {
053        if (this.entry != null) {
054            return;
055        }
056
057        if (entry == null || entry.isDirectory()) {
058            type = FileType.FOLDER;
059        } else {
060            type = FileType.FILE;
061        }
062
063        this.entry = entry;
064    }
065
066    /**
067     * Attaches a child.
068     * <p>
069     * TODO: Shouldn't this method have package-only visibility? Cannot change this without breaking binary
070     * compatibility.
071     * </p>
072     *
073     * @param childName The name of the child.
074     */
075    public void attachChild(final FileName childName) {
076        children.add(childName.getBaseName());
077    }
078
079    /**
080     * Determines if this file can be written to.
081     *
082     * @return {@code true} if this file is writable, {@code false} if not.
083     * @throws FileSystemException if an error occurs.
084     */
085    @Override
086    public boolean isWriteable() throws FileSystemException {
087        return false;
088    }
089
090    /**
091     * Returns the file's type.
092     */
093    @Override
094    protected FileType doGetType() {
095        return type;
096    }
097
098    /**
099     * Lists the children of the file.
100     */
101    @Override
102    protected String[] doListChildren() {
103        try {
104            if (!getType().hasChildren()) {
105                return null;
106            }
107        } catch (final FileSystemException e) {
108            // should not happen as the type has already been cached.
109            throw new RuntimeException(e);
110        }
111
112        return children.toArray(new String[children.size()]);
113    }
114
115    /**
116     * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns
117     * {@link FileType#FILE}.
118     */
119    @Override
120    protected long doGetContentSize() {
121        return entry.getSize();
122    }
123
124    /**
125     * Returns the last modified time of this file.
126     */
127    @Override
128    protected long doGetLastModifiedTime() throws Exception {
129        return entry.getTime();
130    }
131
132    /**
133     * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns
134     * {@link FileType#FILE}. The input stream returned by this method is guaranteed to be closed before this method is
135     * called again.
136     */
137    @Override
138    protected InputStream doGetInputStream() throws Exception {
139        // VFS-210: zip allows to gather an input stream even from a directory and will
140        // return -1 on the first read. getType should not be expensive and keeps the tests
141        // running
142        if (!getType().hasContent()) {
143            throw new FileSystemException("vfs.provider/read-not-file.error", getName());
144        }
145
146        return getAbstractFileSystem().getZipFile().getInputStream(entry);
147    }
148
149    @Override
150    protected void doAttach() throws Exception {
151        getAbstractFileSystem().getZipFile();
152    }
153
154    @Override
155    protected void doDetach() throws Exception {
156        ZipFileSystem afs = getAbstractFileSystem();
157        if (!afs.isOpen()) {
158            afs.close();
159        }
160    }
161}