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.ram;
018
019import java.io.File;
020import java.io.IOException;
021import java.io.Serializable;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.Objects;
027
028import org.apache.commons.vfs2.Capability;
029import org.apache.commons.vfs2.FileContent;
030import org.apache.commons.vfs2.FileName;
031import org.apache.commons.vfs2.FileObject;
032import org.apache.commons.vfs2.FileSystemException;
033import org.apache.commons.vfs2.FileSystemOptions;
034import org.apache.commons.vfs2.FileType;
035import org.apache.commons.vfs2.provider.AbstractFileName;
036import org.apache.commons.vfs2.provider.AbstractFileSystem;
037
038/**
039 * A RAM File System.
040 */
041public class RamFileSystem extends AbstractFileSystem implements Serializable {
042
043    /**
044     * serialVersionUID format is YYYYMMDD for the date of the last binary change.
045     */
046    private static final long serialVersionUID = 20101208L;
047
048    /**
049     * Cache of RAM File Data
050     */
051    private final Map<FileName, RamFileData> cache;
052
053    /**
054     * Constructs a new instance.
055     *
056     * @param rootName The root file name of this file system.
057     * @param fileSystemOptions Options to build this file system.
058     */
059    protected RamFileSystem(final FileName rootName, final FileSystemOptions fileSystemOptions) {
060        super(rootName, null, fileSystemOptions);
061        cache = Collections.synchronizedMap(new HashMap<>());
062        // create root
063        final RamFileData rootData = new RamFileData(rootName);
064        rootData.setType(FileType.FOLDER);
065        rootData.setLastModified(System.currentTimeMillis());
066        cache.put(rootName, rootData);
067    }
068
069    /*
070     * (non-Javadoc)
071     *
072     * @see org.apache.commons.vfs2.provider.AbstractFileSystem#addCapabilities(java.util.Collection)
073     */
074    @Override
075    protected void addCapabilities(final Collection<Capability> caps) {
076        caps.addAll(RamFileProvider.capabilities);
077    }
078
079    /**
080     * Attaches this instance to the given RamFileObject.
081     *
082     * @param ramFileObject A RAM file object.
083     */
084    public void attach(final RamFileObject ramFileObject) {
085        if (ramFileObject.getName() == null) {
086            throw new IllegalArgumentException("Null argument");
087        }
088        RamFileData data = cache.get(ramFileObject.getName());
089        if (data == null) {
090            data = new RamFileData(ramFileObject.getName());
091        }
092        ramFileObject.setData(data);
093    }
094
095    /**
096     * Close the RAMFileSystem.
097     */
098    @Override
099    public void close() {
100        cache.clear();
101        super.close();
102    }
103
104    /*
105     * (non-Javadoc)
106     *
107     * @see org.apache.commons.vfs2.provider.AbstractFileSystem#createFile(org.apache.commons.vfs2.FileName)
108     */
109    @Override
110    protected FileObject createFile(final AbstractFileName name) throws Exception {
111        return new RamFileObject(name, this);
112    }
113
114    /**
115     * Delete a file
116     *
117     * @param file the {@link RamFileObject} file to delete.
118     * @throws FileSystemException Thrown for file system errors.
119     */
120    void delete(final RamFileObject file) throws FileSystemException {
121        // root is read only check
122        FileSystemException.requireNonNull(file.getParent(), "unable to delete root");
123
124        // Remove reference from cache
125        cache.remove(file.getName());
126        // Notify the parent
127        final RamFileObject parent = (RamFileObject) this.resolveFile(file.getParent().getName());
128        parent.getData().removeChild(file.getData());
129        parent.close();
130        // Close the file
131        file.getData().clear();
132        file.close();
133    }
134
135    /**
136     * Import a Tree.
137     *
138     * @param file The File
139     * @throws FileSystemException if an error occurs.
140     */
141    public void importTree(final File file) throws FileSystemException {
142        final FileObject fileFo = getFileSystemManager().toFileObject(file);
143        toRamFileObject(fileFo, fileFo);
144    }
145
146    /**
147     * @param name The name of the file.
148     * @return children The names of the children.
149     */
150    String[] listChildren(final FileName name) {
151        final RamFileData data = cache.get(name);
152        if (data == null || !data.getType().hasChildren()) {
153            return null;
154        }
155        final Collection<RamFileData> children = data.getChildren();
156
157        synchronized (children) {
158            return children.stream().filter(Objects::nonNull).map(childData -> childData.getName().getBaseName()).toArray(String[]::new);
159        }
160    }
161
162    /**
163     * @param from The original file.
164     * @param to The new file.
165     * @throws FileSystemException if an error occurs.
166     */
167    void rename(final RamFileObject from, final RamFileObject to) throws FileSystemException {
168        if (!cache.containsKey(from.getName())) {
169            throw new FileSystemException("File does not exist: " + from.getName());
170        }
171        // Copy data
172
173        to.getData().setContent(from.getData().getContent());
174        to.getData().setLastModified(from.getData().getLastModified());
175        to.getData().setType(from.getData().getType());
176
177        save(to);
178        delete(from);
179    }
180
181    /**
182     * Saves a file
183     *
184     * @param file the {@link RamFileObject} file to save.
185     * @throws FileSystemException Thrown for file system errors.
186     */
187    void save(final RamFileObject file) throws FileSystemException {
188
189        // Validate name
190        if (file.getData().getName() == null) {
191            throw new FileSystemException(new IllegalStateException("The data has no name. " + file));
192        }
193
194        // Add to the parent
195        if (file.getName().getDepth() > 0) {
196            final RamFileData parentData = cache.get(file.getParent().getName());
197            // Only if not already added
198            if (!parentData.hasChildren(file.getData())) {
199                final RamFileObject parent = (RamFileObject) file.getParent();
200                parent.getData().addChild(file.getData());
201                parent.close();
202            }
203        }
204        // Store in cache
205        cache.put(file.getName(), file.getData());
206        file.getData().updateLastModified();
207        file.close();
208    }
209
210    /**
211     * @return the size of the FileSystem
212     */
213    long size() {
214        synchronized (cache) {
215            return cache.values().stream().mapToLong(RamFileData::size).sum();
216        }
217    }
218
219    /**
220     * Import the given file with the name relative to the given root
221     *
222     * @param fo the source {@link FileObject} file to import.
223     * @param root the {@link FileObject} root.
224     * @throws FileSystemException Thrown for file system errors.
225     */
226    private void toRamFileObject(final FileObject fo, final FileObject root) throws FileSystemException {
227        final RamFileObject memFo = (RamFileObject) this
228                .resolveFile(fo.getName().getPath().substring(root.getName().getPath().length()));
229        if (fo.getType().hasChildren()) {
230            // Create Folder
231            memFo.createFolder();
232            // Import recursively
233            final FileObject[] fos = fo.getChildren();
234            for (final FileObject child : fos) {
235                toRamFileObject(child, root);
236            }
237        } else if (fo.isFile()) {
238            // Copy bytes
239            try (FileContent content = fo.getContent()) {
240                content.write(memFo);
241            } catch (final IOException e) {
242                throw new FileSystemException(e.getClass().getName() + " " + e.getMessage());
243            }
244        } else {
245            throw new FileSystemException("File is not a folder nor a file " + memFo);
246        }
247    }
248}