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