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