package net.sourceforge.javautil.common.io.impl;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sourceforge.javautil.common.IOUtil;
import net.sourceforge.javautil.common.io.IVirtualArtifact;
import net.sourceforge.javautil.common.io.VirtualArtifactAbstract;
import net.sourceforge.javautil.common.io.VirtualArtifactException;
import net.sourceforge.javautil.common.io.VirtualArtifactNotFoundException;
import net.sourceforge.javautil.common.io.VirtualArtifactSystem;
import net.sourceforge.javautil.common.io.VirtualArtifactWrapped;
import net.sourceforge.javautil.common.io.IVirtualDirectory;
import net.sourceforge.javautil.common.io.VirtualDirectoryAbstract;
import net.sourceforge.javautil.common.io.IVirtualFile;
import net.sourceforge.javautil.common.io.IVirtualPath;
/**
* An implementation allowing any artifacts to be added/removed dynamically pointing to different actual locations.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: Directory.java 2355 2010-07-27 20:25:32Z ponderator $
*/
public abstract class Directory<O extends IVirtualDirectory> extends VirtualDirectoryAbstract {
protected String name;
protected final O owner;
protected final List<LinkedDirectory> roots = new ArrayList<LinkedDirectory>();
protected final Map<String, IVirtualArtifact> artifacts = new LinkedHashMap<String, IVirtualArtifact>();
protected long modified = System.currentTimeMillis();
public Directory() { this("", null); }
public Directory(String name) { this(name, null); }
public Directory(String name, O owner) {
this.name = name;
this.owner = owner;
}
public boolean isReadOnly() { return false; }
public <T extends IVirtualArtifact> T getArtifact(Class<T> type, IVirtualPath path) {
try {
IVirtualArtifact artifact = path == null ? this : this.getArtifact(path);
if (type.isAssignableFrom(artifact.getClass())) return (T) artifact;
else if (artifact instanceof VirtualArtifactWrapped) { return (T) ((VirtualArtifactWrapped)artifact).unwrap(type); }
if (artifact instanceof Directory) return this.resolve(type, (Directory) artifact);
} catch (VirtualArtifactNotFoundException e) {}
return null;
}
protected <T extends IVirtualArtifact> T resolve (Class<T> type, IVirtualFile file) {
while (true) {
if (file instanceof LinkedFile) file = ((LinkedFile)file).getDelegate();
else if (file instanceof DirectoryFileLinked) file = ((DirectoryFileLinked)file).getDelegate();
else break;
}
return type.isAssignableFrom(file.getClass()) ? (T) file : null;
}
protected <T extends IVirtualArtifact> T resolve (Class<T> type, Directory directory) {
List<LinkedDirectory> roots = directory.getLinkedRoots();
if (type == ILinkedArtifact.class && roots.size() > 0) {
return (T) roots.get(0);
} else if (ISystemArtifact.class.isAssignableFrom(type) && roots.size() > 0) {
IVirtualDirectory search = null;
for (LinkedDirectory root : roots) {
search = root.getDelegate();
while (true) {
if (search instanceof Directory) {
return this.resolve(type, (Directory) search);
} else if (search instanceof LinkedDirectory) {
search = ((LinkedDirectory)search).getDelegate();
} else
break;
}
if (type.isAssignableFrom(search.getClass())) return (T) search;
}
}
return null;
}
public void rename(String newName) {
if (this.owner != null) {
this.owner.remove(this.name);
}
this.name = newName;
this.clearCache();
}
/**
* @return The root directories linked to this one
*/
protected List<LinkedDirectory> getLinkedRoots() {
return this.roots;
}
public long getLength() { return artifacts.size(); }
public URL getURL() {
SystemDirectory sd = getFirst(this);
return sd != null ? sd.getURL() : this.createURL();
}
protected SystemDirectory getFirst (Directory<?> directory) {
for (LinkedDirectory root : directory.getLinkedRoots()) {
IVirtualDirectory dir = root.unwrap(SystemDirectory.class);
if (dir instanceof SystemDirectory) {
return (SystemDirectory) dir;
} else if (dir instanceof Directory) {
SystemDirectory sys = this.getFirst((Directory)dir);
if (sys != null) return sys;
}
}
return null;
}
public long getLastModified() { return this.modified; }
/**
* @return The linked directories
*/
public List<LinkedDirectory> getLinkedDirectories () {
return new ArrayList<LinkedDirectory>( this.getLinkedRoots() );
}
public IVirtualArtifact getArtifact(IVirtualPath path) {
IVirtualArtifact artifact = this;
int idx = 0;
while (path.getPartCount() > idx && artifact instanceof IVirtualDirectory) {
if ("".equals(path.getPart(idx))) { idx++; continue; }
artifact = ((IVirtualDirectory)artifact).getArtifact( path.getPart(idx++) );
}
if (idx != path.getPartCount()) {
for (LinkedDirectory linked : this.getLinkedRoots()) {
try {
return linked.getArtifact(path);
} catch (VirtualArtifactNotFoundException e) {}
}
}
if (artifact == null)
throw new VirtualArtifactNotFoundException(this, this.getPath().append( path.getParts() ).toString("/") + " does not exist.");
return artifact;
}
/**
* This will cause the directory passed to act as part of the root of this directory
* it's files and sub directories are treated as though they are from the root of this
* directory.
*
* @param root The directory to link as a root
*/
public void link (IVirtualDirectory root) {
this.roots.add(new LinkedDirectory(this, root, ""));
}
/**
* Unlink a previously linked directory.
*
* @param path The path that should match a previously linked directories path
*
* @return True if the path was linked and removed, otherwise false
*/
public boolean unlink (IVirtualPath path) {
for (LinkedDirectory linked : this.getLinkedRoots()) {
if (linked.getDelegate().getPath().compareTo(path) == 0) {
this.roots.remove(linked);
return true;
}
}
return false;
}
/**
* This can be used to remove all directory links to this directory
*/
public void removeLinks () {
this.roots.clear();
}
/**
* This assumes the same name as the file
*
* @see #link(IVirtualFile, String)
*/
public DirectoryFileLinked link (IVirtualFile file) { return this.link(file, file.getName()); }
/**
* This allows any file to be linked to this directory, without having to link another directory
* to accomplish the same thing.
*
* @param file The file to link
* @param name The name of the link
*
* @return A file wrapper representing the link
*/
public DirectoryFileLinked link (IVirtualFile file, String name) {
DirectoryFileLinked linked = new DirectoryFileLinked(this, file, name);
this.artifacts.put(name, linked);
return linked;
}
public IVirtualArtifact getArtifact(String name) {
IVirtualArtifact artifact = artifacts.get(name);
if (artifact == null) {
for (LinkedDirectory linked : this.getLinkedRoots()) {
artifact = linked.getArtifact(name);
if (artifact != null)
break;
}
}
if (artifact instanceof LinkedDirectory) {
return this.createDirectory(name);
}
return artifact;
}
public Directory createDirectory(String name) {
Directory dir = null;
IVirtualArtifact artifact = this.artifacts.get(name);
if (artifact instanceof IVirtualFile) {
throw new VirtualArtifactException(this, "A file already exists by this name: " + name);
} else if (artifact != null) {
dir = (Directory) artifact;
} else {
SystemDirectory system = this.getFirst(this);
if (system != null && this.isCreateLinkedArtifacts()) { system.createDirectory(name); }
dir = new DirectoryInternal(name, this);
this.artifacts.put(name, dir);
}
return dir;
}
/**
* @return True if created artifacts should also be created in possibly linked {@link ISystemArtifact} targets.
*/
public abstract boolean isCreateLinkedArtifacts();
/**
* This allows more control over linked directory related creations.
* If there are linked directories to this one then the desired functionality
* may be that a real file is created. Calls to {@link #createFile(String)}
* will default to whatever is returned by {@link #isCreateLinkedFiles()}.
*
* @param name The name the new file
* @param real True if a real file should be created in the case there are directories linked to this one
* @return The new file created
*/
public IVirtualFile createFile(String name, boolean real) {
SystemDirectory system = this.getFirst(this);
return system == null || !real ? new DirectoryFile(name, this) : this.link( system.createFile(name) );
}
public IVirtualFile createFile(String name) { return this.createFile(name, this.isCreateLinkedArtifacts()); }
/**
* @param name The name of the new file
* @param url The URL that forms the source for input/output for the file
* @return A new file that will automatically be created in this directory
*/
public IVirtualFile createFile(String name, URL url) {
IVirtualFile file = new DirectoryFile(name, this);
file.setIOHandler(new URLIOHandler(url));
this.artifacts.put(name, file);
return file;
}
public boolean makeDirectories() {
if (owner != null) return owner.makeDirectories();
return true;
}
public boolean makeDirectory() { return true; }
public boolean remove(String name) { artifacts.remove(name); return true; }
public Iterator<IVirtualArtifact> getArtifacts() {
Set<IVirtualArtifact> artifacts = new LinkedHashSet<IVirtualArtifact>(this.artifacts.values());
for (LinkedDirectory root : this.getLinkedRoots()) {
Iterator<IVirtualArtifact> ra = root.getArtifacts();
while (ra.hasNext()) {
IVirtualArtifact artifact = ra.next();
if (artifact instanceof LinkedDirectory) {
artifacts.add(this.getDirectory(artifact.getName(), true));
} else
artifacts.add(artifact);
}
}
return artifacts.iterator();
}
public String getName() { return name; }
public IVirtualDirectory getOwner() { return owner; }
public boolean isExists() { return true; }
public boolean remove() {
if (this.owner != null)
return this.owner.remove(this.name);
return true;
}
public String toString () { return (owner == null ? "Root" : owner) + ("".equals(name) ? "" : ("->[" + name + "]")); }
}