/*
* Copyright (C) 2009 by TunedIT. All rights reserved.
*
*/
package org.tunedit.core;
import java.io.InputStream;
import java.util.List;
import org.debellor.base.Buffer;
import org.debellor.core.Cell;
import org.debellor.core.exception.cell.CellException;
import org.debellor.rseslib.RseslibClassifier;
import org.debellor.weka.ArffReader;
import org.debellor.weka.WekaClassifier;
import org.tunedit.core.exception.AlgorithmErrorException;
import org.tunedit.core.exception.ClassExpectedException;
import org.tunedit.core.exception.TunedTesterException;
/**
* <p>StandardLoader is a wrapper class for {@link ResourceLoader} -
* it extends ResourceLoader with convenient methods for simultaneous
* loading and converting resources to appropriate classes or objects,
* as expected by the client code (evaluation procedure).
* Thus, StandardLoader not only loads but also <i>interprets</i>
* the resource according to the client's request.
* For example, if the client expects the resource to be a Debellor's Cell subclass,
* StandardLoader will try to cast the downloaded class to Cell
* or - if this fails - to interpret the class as Weka's or Rseslib's classifier,
* which then can be wrapped up into a cell using Debellor's standard wrappers:
* WekaClassifier or RseslibClassifier.
* </p>
* <p>
* StandardLoader facilitates loading of resources from TunedIT Repository
* and instantiating them as objects of proper types.
* This makes implementation of evaluation procedures easier
* and more versatile, as addition of new types of algorithms
* or data storage formats can be done through extending
* StandardLoader alone, without touching the code of evaluation procedures.
* </p>
*
* @author Marcin Wojnarski
*
*/
public class StandardLoader implements ResourceLoader {
private ResourceLoader loader;
public StandardLoader(ResourceLoader loader) {
this.loader = loader;
}
@Override
public ExternalProcess runProcess(ResourceName fileResource, List<String> args) throws TunedTesterException{
return loader.runProcess(fileResource, args);
}
// TODO loadCell should allocate objects of different types reflectively, to avoid compilation-time dependencies (needed to support versioning)
/**
* Loads the resource from TunedIT Repository and attempts to
* interpret it as Debellor's Cell subclass.
* Then constructs an instance of this class.
* Currently, the resource must be either a Cell subclass
* or a Weka's or Rseslib's classifier (subclass of
* <code>weka.classifiers.Classifier</code>
* or <code>rseslib.processing.classification.Classifier</code>).
*
* @param classResource name of TunedIT resource, the resource must be a Java class
* @return an instance of Cell subclass represented by the resource
* @throws TunedTesterException
*/
@SuppressWarnings("unchecked")
public Cell loadCell(ResourceName classResource) throws TunedTesterException, AlgorithmErrorException {
Class<?> c = loadClass(classResource);
try {
if(isSubclass(c, "org.debellor.core.Cell"))
return (Cell) c.newInstance();
if(isSubclass(c, "weka.classifiers.Classifier"))
return new WekaClassifier((Class<? extends weka.classifiers.Classifier>) c);
if(isSubclass(c, "rseslib.processing.classification.Classifier"))
return new RseslibClassifier((Class<? extends rseslib.processing.classification.Classifier>) c);
}
catch(Exception e) {
throw new AlgorithmErrorException(e);
}
throw new AlgorithmErrorException("Class " + classResource + " is neither a cell nor can be converted to a cell");
}
/**
* Loads Java class represented by <code>classResource</code> from TunedIT Repository
* and dynamically links into TunedTester. Returns the loaded class.
*
* @param classResource name of TunedIT resource, the resource must be a Java class
* @return representation of the class as an instance of <code>java.lang.Class</code>
* @throws TunedTesterException
*/
public Class<?> loadClass(ResourceName classResource) throws TunedTesterException {
verifyClassLoader();
String className = classResource.getClassPart();
if (className == null){
throw new ClassExpectedException(classResource);
}
try {
return Class.forName(className);
}
catch (ClassNotFoundException e) {
throw new TunedTesterException("Class " + className + " cannot be found", e);
}
}
/** Loads data from an ARFF file, buffers them in memory and returns
* in the form of a Debellor's cell.
* Data can be accessed by calling open()-next()...-close() on the returned cell.
* This cell can be opened many times and it will produce always the same
* sequence of samples.
*
* @param dataset name of a TunedIT resource, the resource must be an ARFF file
*/
public Cell loadData(ResourceName dataset) throws TunedTesterException {
String s = dataset.getFilePart();
if ((s != null) && (s.endsWith(".arff"))) {
try {
ArffReader reader = new ArffReader();
reader.setInputStream(open(dataset));
reader.set("decisionAttr", "last");
Cell buffer = new Buffer();
buffer.setSource(reader);
buffer.learn(); // materialize input data in memory
buffer.setSource(null); // release 'reader' to be garbage-collected
return buffer;
} catch (CellException e) {
throw new TunedTesterException(e);
}
} else {
Class<?> c = loadClass(dataset);
try {
if(isSubclass(c, "org.debellor.core.Cell"))
return (Cell) c.newInstance();
} catch (Exception e) {
throw new TunedTesterException(e);
}
throw new TunedTesterException();
}
}
@Override
public InputStream open(ResourceName resource) throws TunedTesterException {
return loader.open(resource);
}
/**
* @param cls - class to be checked
* @param baseClass - name of the hypothetical superclass or interface of cls
* @return <code>true</code> if <code>cls</code> subclasses/implements the class/interface <code>baseClass</code>
* @throws TunedTesterException
*/
private boolean isSubclass(Class<?> cls, String baseClass) throws TunedTesterException {
verifyClassLoader();
try {
return Class.forName(baseClass).isAssignableFrom(cls);
} catch (ClassNotFoundException e) {
return false;
}
}
private void verifyClassLoader() throws TunedTesterException {
try {
ClassLoader sys = ClassLoader.getSystemClassLoader();
// if we get here it means that there is something wrong with class loaders
if(getClass().getClassLoader() == sys)
throw new TunedTesterException("StandardLoader was loaded by system ClassLoader");
else
throw new TunedTesterException("StandardLoader has access rights to get system ClassLoader");
}
catch(SecurityException e) {
// SecurityException should occur, which indicates that this StandardLoader
// has been loaded properly with a custom TunedTester's class loader,
// not with the system class loader.
return;
}
}
}