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; 018 019import java.io.InputStream; 020import java.io.OutputStream; 021import java.security.cert.Certificate; 022import java.util.HashSet; 023import java.util.Map; 024import java.util.Objects; 025import java.util.Set; 026import java.util.stream.Stream; 027 028import org.apache.commons.lang3.ArrayUtils; 029import org.apache.commons.vfs2.FileChangeEvent; 030import org.apache.commons.vfs2.FileContent; 031import org.apache.commons.vfs2.FileContentInfo; 032import org.apache.commons.vfs2.FileListener; 033import org.apache.commons.vfs2.FileName; 034import org.apache.commons.vfs2.FileNotFolderException; 035import org.apache.commons.vfs2.FileObject; 036import org.apache.commons.vfs2.FileSystemException; 037import org.apache.commons.vfs2.FileType; 038import org.apache.commons.vfs2.RandomAccessContent; 039import org.apache.commons.vfs2.util.RandomAccessMode; 040import org.apache.commons.vfs2.util.WeakRefFileListener; 041 042/** 043 * A file backed by another file. 044 * <p> 045 * TODO - Extract subclass that overlays the children. 046 * </p> 047 * 048 * @param <AFS> A subclass of AbstractFileSystem. 049 */ 050public class DelegateFileObject<AFS extends AbstractFileSystem> extends AbstractFileObject<AFS> implements FileListener { 051 052 private FileObject fileObject; 053 private final Set<String> children = new HashSet<>(); 054 private boolean ignoreEvent; 055 056 /** 057 * Constructs a new instance. 058 * 059 * @param fileName the file name. 060 * @param fileSystem the file system. 061 * @param fileObject My file object. 062 * @throws FileSystemException For subclasses to throw. 063 */ 064 public DelegateFileObject(final AbstractFileName fileName, final AFS fileSystem, final FileObject fileObject) throws FileSystemException { 065 super(fileName, fileSystem); 066 this.fileObject = fileObject; 067 if (fileObject != null) { 068 WeakRefFileListener.installListener(fileObject, this); 069 } 070 } 071 072 /** 073 * Adds a child to this file. 074 * 075 * @param baseName The base FileName. 076 * @param type The FileType. 077 * @throws Exception if an error occurs. 078 */ 079 public void attachChild(final FileName baseName, final FileType type) throws Exception { 080 final FileType oldType = doGetType(); 081 if (children.add(baseName.getBaseName())) { 082 childrenChanged(baseName, type); 083 } 084 maybeTypeChanged(oldType); 085 } 086 087 /** 088 * Close the delegated file. 089 * 090 * @throws FileSystemException if an error occurs. 091 */ 092 @Override 093 public void close() throws FileSystemException { 094 super.close(); 095 096 if (fileObject != null) { 097 fileObject.close(); 098 } 099 } 100 101 /** 102 * Creates this file as a folder. 103 */ 104 @Override 105 protected void doCreateFolder() throws Exception { 106 ignoreEvent = true; 107 try { 108 fileObject.createFolder(); 109 } finally { 110 ignoreEvent = false; 111 } 112 } 113 114 /** 115 * Deletes the file. 116 */ 117 @Override 118 protected void doDelete() throws Exception { 119 ignoreEvent = true; 120 try { 121 fileObject.delete(); 122 } finally { 123 ignoreEvent = false; 124 } 125 } 126 127 /** 128 * Returns the attributes of this file. 129 */ 130 @Override 131 protected Map<String, Object> doGetAttributes() throws Exception { 132 return getFileContent().getAttributes(); 133 } 134 135 /** 136 * Returns the certificates of this file. 137 */ 138 @Override 139 protected Certificate[] doGetCertificates() throws Exception { 140 return getFileContent().getCertificates(); 141 } 142 143 /** 144 * Gets file content info. 145 * 146 * @return the file content info of the delegee. 147 * @throws Exception Any thrown Exception is wrapped in FileSystemException. 148 * @since 2.0 149 */ 150 protected FileContentInfo doGetContentInfo() throws Exception { 151 return getFileContent().getContentInfo(); 152 } 153 154 /** 155 * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns 156 * {@link FileType#FILE}. 157 */ 158 @Override 159 protected long doGetContentSize() throws Exception { 160 return getFileContent().getSize(); 161 } 162 163 /** 164 * Creates an input stream to read the file content from. 165 */ 166 @Override 167 protected InputStream doGetInputStream(final int bufferSize) throws Exception { 168 return getFileContent().getInputStream(bufferSize); 169 } 170 171 /** 172 * Returns the last-modified time of this file. 173 */ 174 @Override 175 protected long doGetLastModifiedTime() throws Exception { 176 return getFileContent().getLastModifiedTime(); 177 } 178 179 /** 180 * Creates an output stream to write the file content to. 181 */ 182 @Override 183 protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception { 184 return getFileContent().getOutputStream(bAppend); 185 } 186 187 /** 188 * Creates access to the file for random I/O. 189 * 190 * @since 2.0 191 */ 192 @Override 193 protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception { 194 return getFileContent().getRandomAccessContent(mode); 195 } 196 197 /** 198 * Determines the type of the file, returns null if the file does not exist. 199 */ 200 @Override 201 protected FileType doGetType() throws FileSystemException { 202 if (fileObject != null) { 203 return fileObject.getType(); 204 } 205 if (children.isEmpty()) { 206 return FileType.IMAGINARY; 207 } 208 return FileType.FOLDER; 209 } 210 211 /** 212 * Determines if this file is executable. 213 */ 214 @Override 215 protected boolean doIsExecutable() throws FileSystemException { 216 if (fileObject != null) { 217 return fileObject.isExecutable(); 218 } 219 return false; 220 } 221 222 /** 223 * Determines if this file is hidden. 224 */ 225 @Override 226 protected boolean doIsHidden() throws FileSystemException { 227 if (fileObject != null) { 228 return fileObject.isHidden(); 229 } 230 return false; 231 } 232 233 /** 234 * Determines if this file can be read. 235 */ 236 @Override 237 protected boolean doIsReadable() throws FileSystemException { 238 if (fileObject != null) { 239 return fileObject.isReadable(); 240 } 241 return true; 242 } 243 244 /** 245 * Determines if this file can be written to. 246 */ 247 @Override 248 protected boolean doIsWriteable() throws FileSystemException { 249 if (fileObject != null) { 250 return fileObject.isWriteable(); 251 } 252 return false; 253 } 254 255 /** 256 * Lists the children of the file. 257 */ 258 @Override 259 protected String[] doListChildren() throws Exception { 260 if (fileObject != null) { 261 final FileObject[] children; 262 263 try { 264 children = fileObject.getChildren(); 265 } catch (final FileNotFolderException e) { 266 // VFS-210 267 throw new FileNotFolderException(getName(), e); 268 } 269 270 return Stream.of(children).filter(Objects::nonNull).map(child -> child.getName().getBaseName()).toArray(String[]::new); 271 } 272 return children.toArray(ArrayUtils.EMPTY_STRING_ARRAY); 273 } 274 275 /** 276 * Removes an attribute of this file. 277 * 278 * @since 2.0 279 */ 280 @Override 281 protected void doRemoveAttribute(final String attrName) throws Exception { 282 getFileContent().removeAttribute(attrName); 283 } 284 285 /** 286 * Renames the file. 287 * 288 * @param newFile the new location/name. 289 * @throws Exception Any thrown Exception is wrapped in FileSystemException. 290 * @since 2.0 291 */ 292 @Override 293 protected void doRename(final FileObject newFile) throws Exception { 294 fileObject.moveTo(((DelegateFileObject) newFile).fileObject); 295 } 296 297 /** 298 * Sets an attribute of this file. 299 */ 300 @Override 301 protected void doSetAttribute(final String attrName, final Object value) throws Exception { 302 getFileContent().setAttribute(attrName, value); 303 } 304 305 /** 306 * Sets the last-modified time of this file. 307 * 308 * @since 2.0 309 */ 310 @Override 311 protected boolean doSetLastModifiedTime(final long modtime) throws Exception { 312 getFileContent().setLastModifiedTime(modtime); 313 return true; 314 } 315 316 /** 317 * Called when a file is changed. 318 * <p> 319 * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}. 320 * </p> 321 * 322 * @param event The FileChangeEvent. 323 * @throws Exception if an error occurs. 324 */ 325 @Override 326 public void fileChanged(final FileChangeEvent event) throws Exception { 327 if (event.getFileObject() != fileObject) { 328 return; 329 } 330 if (!ignoreEvent) { 331 handleChanged(); 332 } 333 } 334 335 /** 336 * Called when a file is created. 337 * 338 * @param event The FileChangeEvent. 339 * @throws Exception if an error occurs. 340 */ 341 @Override 342 public void fileCreated(final FileChangeEvent event) throws Exception { 343 if (event.getFileObject() != fileObject) { 344 return; 345 } 346 if (!ignoreEvent) { 347 handleCreate(fileObject.getType()); 348 } 349 } 350 351 /** 352 * Called when a file is deleted. 353 * 354 * @param event The FileChangeEvent. 355 * @throws Exception if an error occurs. 356 */ 357 @Override 358 public void fileDeleted(final FileChangeEvent event) throws Exception { 359 if (event.getFileObject() != fileObject) { 360 return; 361 } 362 if (!ignoreEvent) { 363 handleDelete(); 364 } 365 } 366 367 /** 368 * Gets access to the delegated file. 369 * 370 * @return The FileObject. 371 * @since 2.0 372 */ 373 public FileObject getDelegateFile() { 374 return fileObject; 375 } 376 377 FileContent getFileContent() throws FileSystemException { 378 return fileObject.getContent(); 379 } 380 381 /** 382 * Checks whether the file's type has changed, and fires the appropriate events. 383 * 384 * @param oldType The old FileType. 385 * @throws Exception if an error occurs. 386 */ 387 private void maybeTypeChanged(final FileType oldType) throws Exception { 388 final FileType newType = doGetType(); 389 if (oldType == FileType.IMAGINARY && newType != FileType.IMAGINARY) { 390 handleCreate(newType); 391 } else if (oldType != FileType.IMAGINARY && newType == FileType.IMAGINARY) { 392 handleDelete(); 393 } 394 } 395 396 /** 397 * Refresh file information. 398 * 399 * @throws FileSystemException if an error occurs. 400 * @since 2.0 401 */ 402 @Override 403 public void refresh() throws FileSystemException { 404 super.refresh(); 405 if (fileObject != null) { 406 fileObject.refresh(); 407 } 408 } 409 410 /** 411 * Attaches or detaches the target file. 412 * 413 * @param fileObject The FileObject. 414 * @throws Exception if an error occurs. 415 */ 416 public void setFile(final FileObject fileObject) throws Exception { 417 final FileType oldType = doGetType(); 418 if (fileObject != null) { 419 WeakRefFileListener.installListener(fileObject, this); 420 } 421 this.fileObject = fileObject; 422 maybeTypeChanged(oldType); 423 } 424}