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.io.function.Uncheck;
024import org.apache.commons.lang3.ArrayUtils;
025import org.apache.commons.vfs2.FileName;
026import org.apache.commons.vfs2.FileSystemException;
027import org.apache.commons.vfs2.FileType;
028import org.apache.commons.vfs2.provider.AbstractFileName;
029import org.apache.commons.vfs2.provider.AbstractFileObject;
030
031/**
032 * A file in a ZIP file system.
033 */
034public class ZipFileObject extends AbstractFileObject<ZipFileSystem> {
035
036    /** The ZipEntry. */
037    protected ZipEntry entry;
038    private final HashSet<String> children = new HashSet<>();
039    private FileType type;
040
041    /**
042     * Constructs a new instance.
043     *
044     * @param fileName the file name.
045     * @param entry The zip entry.
046     * @param fileSystem the file system.
047     * @param zipExists whether the zip file exists.
048     */
049    protected ZipFileObject(final AbstractFileName fileName, final ZipEntry entry, final ZipFileSystem fileSystem, final boolean zipExists) {
050        super(fileName, fileSystem);
051        setZipEntry(entry);
052        if (!zipExists) {
053            type = FileType.IMAGINARY;
054        }
055    }
056
057    /**
058     * Attaches a child.
059     * <p>
060     * TODO: Shouldn't this method have package-only visibility? Cannot change this without breaking binary
061     * compatibility.
062     * </p>
063     *
064     * @param childName The name of the child.
065     */
066    public void attachChild(final FileName childName) {
067        children.add(childName.getBaseName());
068    }
069
070    @Override
071    protected void doAttach() throws Exception {
072        getAbstractFileSystem().getZipFile();
073    }
074
075    @Override
076    protected void doDetach() throws Exception {
077        final ZipFileSystem afs = getAbstractFileSystem();
078        if (!afs.isOpen()) {
079            afs.close();
080        }
081    }
082
083    /**
084     * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns
085     * {@link FileType#FILE}.
086     */
087    @Override
088    protected long doGetContentSize() {
089        return entry.getSize();
090    }
091
092    /**
093     * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns
094     * {@link FileType#FILE}. The input stream returned by this method is guaranteed to be closed before this method is
095     * called again.
096     */
097    @Override
098    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
099        // VFS-210: zip allows to gather an input stream even from a directory and will
100        // return -1 on the first read. getType should not be expensive and keeps the tests
101        // running
102        if (!getType().hasContent()) {
103            throw new FileSystemException("vfs.provider/read-not-file.error", getName());
104        }
105
106        return getAbstractFileSystem().getZipFile().getInputStream(entry);
107    }
108
109    /**
110     * Returns the last modified time of this file.
111     */
112    @Override
113    protected long doGetLastModifiedTime() throws Exception {
114        return entry.getTime();
115    }
116
117    /**
118     * Returns the file's type.
119     */
120    @Override
121    protected FileType doGetType() {
122        return type;
123    }
124
125    /**
126     * Lists the children of the file.
127     */
128    @Override
129    protected String[] doListChildren() {
130        if (!Uncheck.get(this::getType).hasChildren()) {
131            return null;
132        }
133        return children.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
134    }
135
136    /**
137     * Determines if this file can be written to.
138     *
139     * @return {@code true} if this file is writable, {@code false} if not.
140     * @throws FileSystemException if an error occurs.
141     */
142    @Override
143    public boolean isWriteable() throws FileSystemException {
144        return false;
145    }
146
147    /**
148     * Sets the details for this file object.
149     *
150     * @param entry ZIP information related to this file.
151     */
152    protected void setZipEntry(final ZipEntry entry) {
153        if (this.entry != null) {
154            return;
155        }
156
157        if (entry == null || entry.isDirectory()) {
158            type = FileType.FOLDER;
159        } else {
160            type = FileType.FILE;
161        }
162
163        this.entry = entry;
164    }
165}