package railo.commons.io.res.type.cache;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import railo.commons.io.ModeUtil;
import railo.commons.io.res.ContentType;
import railo.commons.io.res.Resource;
import railo.commons.io.res.ResourceMetaData;
import railo.commons.io.res.ResourceProvider;
import railo.commons.io.res.util.ResourceSupport;
import railo.commons.io.res.util.ResourceUtil;
import railo.runtime.exp.PageRuntimeException;
import railo.runtime.op.Caster;
import railo.runtime.type.Struct;
/**
* a ram resource
*/
public final class CacheResource extends ResourceSupport implements ResourceMetaData {
private final CacheResourceProvider provider;
private final String parent;
private final String name;
//private CacheResourceCore _core;
CacheResource(CacheResourceProvider provider, String path) {
this.provider=provider;
if(path.equals("/")) {
this.parent=null;
this.name="";
}
else {
String[] pn = ResourceUtil.translatePathName(path);
this.parent=pn[0];
this.name=pn[1];
}
}
private CacheResource(CacheResourceProvider provider, String parent,String name) {
this.provider=provider;
this.parent=parent ;
this.name=name;
}
private CacheResourceCore getCore() {
return provider.getCore(parent,name);
}
private void removeCore() throws IOException {
provider.removeCore(parent,name);
}
private CacheResourceCore createCore(int type) throws IOException {
return provider.createCore(parent,name,type);
}
private void touch() {
provider.touch(parent,name);
}
@Override
public String getPath() {
return provider.getScheme().concat("://").concat(getInnerPath());
}
private String getInnerPath() {
if(parent==null) return "/";
return parent.concat(name);
}
@Override
public String getName() {
return name;
}
@Override
public String getParent() {
if(isRoot()) return null;
return provider.getScheme().concat("://").concat(ResourceUtil.translatePath(parent, true, false));
}
@Override
public boolean isReadable() {
return ModeUtil.isReadable(getMode());
}
@Override
public boolean isWriteable() {
return ModeUtil.isWritable(getMode());
}
@Override
public void remove(boolean force) throws IOException {
if(isRoot())
throw new IOException("can't remove root resource ["+getPath()+"]");
provider.read(this);
CacheResourceCore core = getCore();
if(core==null)
throw new IOException("can't remove resource ["+getPath()+"],resource does not exist");
Resource[] children = listResources();
if(children!=null && children.length>0) {
if(!force) {
throw new IOException("can't delete directory ["+getPath()+"], directory is not empty");
}
for(int i=0;i<children.length;i++) {
children[i].remove(true);
}
}
removeCore();
}
@Override
public boolean exists() {
try {
provider.read(this);
} catch (IOException e) {
return true;
}
return getCore()!=null;
}
@Override
public Resource getParentResource() {
return getParentRamResource();
}
private CacheResource getParentRamResource() {
if(isRoot()) return null;
return new CacheResource(provider,parent);
}
public Resource getRealResource(String realpath) {
realpath=ResourceUtil.merge(getInnerPath(), realpath);
if(realpath.startsWith("../"))return null;
return new CacheResource(provider,realpath);
}
@Override
public boolean isAbsolute() {
return true;
}
@Override
public boolean isDirectory() {
return exists() && getCore().getType()==CacheResourceCore.TYPE_DIRECTORY;
}
@Override
public boolean isFile() {
return exists() && getCore().getType()==CacheResourceCore.TYPE_FILE;
}
@Override
public long lastModified() {
if(!exists()) return 0;
return getCore().getLastModified();
}
@Override
public long length() {
if(!exists()) return 0;
byte[] data= getCore().getData();
if(data==null) return 0;
return data.length;
}
@Override
public String[] list() {
if(!exists()) return null;
CacheResourceCore core = getCore();
if(core.getType()!=CacheResourceCore.TYPE_DIRECTORY)
return null;
try {
return provider.getChildNames(getInnerPath());
} catch (IOException e) {
throw new PageRuntimeException(Caster.toPageException(e));
}
}
@Override
public Resource[] listResources() {
String[] list = list();
if(list==null)return null;
Resource[] children=new Resource[list.length];
String p=getInnerPath();
if(!isRoot())p=p.concat("/");
for(int i=0;i<children.length;i++) {
children[i]=new CacheResource(provider,p,list[i]);
}
return children;
}
@Override
public boolean setLastModified(long time) {
if(!exists()) return false;
getCore().setLastModified(time);
return true;
}
@Override
public boolean setReadOnly() {
return setWritable(false);
}
@Override
public void createFile(boolean createParentWhenNotExists) throws IOException {
ResourceUtil.checkCreateFileOK(this,createParentWhenNotExists);
provider.lock(this);
try {
createCore(CacheResourceCore.TYPE_FILE);
}
finally {
provider.unlock(this);
}
}
@Override
public void createDirectory(boolean createParentWhenNotExists) throws IOException {
ResourceUtil.checkCreateDirectoryOK(this,createParentWhenNotExists);
provider.lock(this);
try {
createCore(CacheResourceCore.TYPE_DIRECTORY);
}
finally {
provider.unlock(this);
}
}
@Override
public InputStream getInputStream() throws IOException {
ResourceUtil.checkGetInputStreamOK(this);
provider.lock(this);
CacheResourceCore core = getCore();
byte[] data = core.getData();
if(data==null)data=new byte[0];
provider.unlock(this);
return new ByteArrayInputStream(data);
}
public OutputStream getOutputStream(boolean append) throws IOException {
ResourceUtil.checkGetOutputStreamOK(this);
provider.lock(this);
return new CacheOutputStream(this,append);
}
public ContentType getContentType() {
return ResourceUtil.getContentType(this);
}
@Override
public ResourceProvider getResourceProvider() {
return provider;
}
@Override
public String toString() {
return getPath();
}
/**
* This is useed by the MemoryResource too write back data to, that are written to outputstream
*/
class CacheOutputStream extends ByteArrayOutputStream {
private CacheResource res;
private boolean append;
/**
* Constructor of the class
* @param res
*/
public CacheOutputStream(CacheResource res, boolean append) {
this.append=append;
this.res=res;
}
@Override
public void close() throws IOException {
try {
super.close();
CacheResourceCore core = res.getCore();
if(core==null)core=res.createCore(CacheResourceCore.TYPE_FILE);
else core.setLastModified(System.currentTimeMillis());
core.setData(this.toByteArray(),append);
touch();
}
finally {
res.getResourceProvider().unlock(res);
}
}
}
@Override
public boolean setReadable(boolean value) {
if(!exists())return false;
try {
setMode(ModeUtil.setReadable(getMode(), value));
return true;
} catch (IOException e) {
return false;
}
}
@Override
public boolean setWritable(boolean value) {
if(!exists())return false;
try {
setMode(ModeUtil.setWritable(getMode(), value));
return true;
} catch (IOException e) {
return false;
}
}
private boolean isRoot() {
return parent==null;
}
public int getMode() {
if(!exists())return 0;
return getCore().getMode();
}
public void setMode(int mode) throws IOException {
if(!exists())throw new IOException("can't set mode on resource ["+this+"], resource does not exist");
getCore().setMode(mode);
}
@Override
public boolean getAttribute(short attribute) {
if(!exists())return false;
return (getCore().getAttributes()&attribute)>0;
}
@Override
public void setAttribute(short attribute, boolean value) throws IOException {
if(!exists())throw new IOException("can't get attributes on resource ["+this+"], resource does not exist");
int attr = getCore().getAttributes();
if(value) {
if((attr&attribute)==0) attr+=attribute;
}
else {
if((attr&attribute)>0) attr-=attribute;
}
getCore().setAttributes(attr);
}
public Struct getMetaData() {
return provider.getMeta(parent,name);
}
}