View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.provider.ram;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.io.Serializable;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Map;
26  import java.util.Objects;
27  
28  import org.apache.commons.vfs2.Capability;
29  import org.apache.commons.vfs2.FileContent;
30  import org.apache.commons.vfs2.FileName;
31  import org.apache.commons.vfs2.FileObject;
32  import org.apache.commons.vfs2.FileSystemException;
33  import org.apache.commons.vfs2.FileSystemOptions;
34  import org.apache.commons.vfs2.FileType;
35  import org.apache.commons.vfs2.provider.AbstractFileName;
36  import org.apache.commons.vfs2.provider.AbstractFileSystem;
37  
38  /**
39   * A RAM File System.
40   */
41  public class RamFileSystem extends AbstractFileSystem implements Serializable {
42  
43      /**
44       * serialVersionUID format is YYYYMMDD for the date of the last binary change.
45       */
46      private static final long serialVersionUID = 20101208L;
47  
48      /**
49       * Cache of RAM File Data
50       */
51      private final Map<FileName, RamFileData> cache;
52  
53      /**
54       * Constructs a new instance.
55       *
56       * @param rootName The root file name of this file system.
57       * @param fileSystemOptions Options to build this file system.
58       */
59      protected RamFileSystem(final FileName rootName, final FileSystemOptions fileSystemOptions) {
60          super(rootName, null, fileSystemOptions);
61          cache = Collections.synchronizedMap(new HashMap<>());
62          // create root
63          final RamFileData rootData = new RamFileData(rootName);
64          rootData.setType(FileType.FOLDER);
65          rootData.setLastModified(System.currentTimeMillis());
66          cache.put(rootName, rootData);
67      }
68  
69      /*
70       * (non-Javadoc)
71       *
72       * @see org.apache.commons.vfs2.provider.AbstractFileSystem#addCapabilities(java.util.Collection)
73       */
74      @Override
75      protected void addCapabilities(final Collection<Capability> caps) {
76          caps.addAll(RamFileProvider.capabilities);
77      }
78  
79      /**
80       * Attaches this instance to the given RamFileObject.
81       *
82       * @param ramFileObject A RAM file object.
83       */
84      public void attach(final RamFileObject ramFileObject) {
85          if (ramFileObject.getName() == null) {
86              throw new IllegalArgumentException("Null argument");
87          }
88          RamFileData data = cache.get(ramFileObject.getName());
89          if (data == null) {
90              data = new RamFileData(ramFileObject.getName());
91          }
92          ramFileObject.setData(data);
93      }
94  
95      /**
96       * Close the RAMFileSystem.
97       */
98      @Override
99      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 }