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}