package fr.unistra.pelican;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.junit.Assert;
/**
*
* @author sollier,lefevre
*
* Abstract base class for all algorithms.
* <p>
* An algorithm is a process that takes a list of parameters and returns
* another list of parameters.
* </p>
*
* <p>
* Use of an algorithm takes four steps:<br/>
* - First, creation of an algorithm's intance:<br/>
* eg. Algorithm algorithm = new
* fr.unistra.pelican.algorithms.arithmetic.Inversion; <br/>
* - Then, set the input parameters: <br/>
* algorithm.setInput(inputList); <br/>
* - Let the algorithm process the inputs: <br/>
* algorithm.launch(); <br/>
* - Finally grab the output: <br/>
* ArrayList outputList = algorithm.getOutput() <br/>
* </p>
* *
*
* <p>
* The number of input and output parameters can be retrieved by using
* getInputTypes().length and getOutputTypes().length
* </p>
*
*/
public abstract class Algorithm {
/***************************************************************************
*
*
* Attributes
*
*
**************************************************************************/
/**
* List containing the output objects
*/
private ArrayList<Object> output = new ArrayList<Object>();
/**
* List containing the input objects
*/
private ArrayList<Object> input = new ArrayList<Object>();
/**
* List containing the option objects
*/
private ArrayList<Object> option = new ArrayList<Object>();
/**
* String containing the description of the extended algorithm
*/
public String help;
/**
* String containing the name of the input attributes of the extended
* algorithm
*/
public String inputs = "";
/**
* String containing the name of the option attributes of the extended
* algorithm
*/
public String options = "";
/**
* String containing the name of the input attributes of the extended
* algorithm
*/
public String outputs = "";
/**
* String containing the suffix of inputs for test cases
*/
private final static String INPUT_SUFFIX = ".inputs";
/**
* String containing the suffix of outputs for test cases
*/
private final static String OUTPUT_SUFFIX = ".outputs";
/***************************************************************************
*
*
* Methods
*
*
**************************************************************************/
/**
* Abstract method to implement in each inherited algorithm.
*
* @throws AlgorithmException
*/
public abstract void launch() throws AlgorithmException;
/**
*
* Grab the input attributes of the inherited algorithms and set them into
* the input ArrayList
*
* @param inputList
* list of the input attributes
* @throws InvalidNumberOfParametersException
* @throws InvalidTypeOfParameterException
*/
public void setInput(ArrayList<Object> inputList)
throws InvalidNumberOfParametersException,
InvalidTypeOfParameterException {
// Cleaning input and output parameters
input.clear();
option.clear();
// parsing
ArrayList<String> inputResult = parser(inputs);
ArrayList<String> optionResult = parser(options);
// Checking if the number of given parameters is at least
// the number of mandatory parameters and less than the full set of
// parameters
if (inputList.size() < inputResult.size()
|| inputList.size() > inputResult.size() + optionResult.size())
throw new InvalidNumberOfParametersException(
"Number of parameters is incorrect : " + inputList.size()
+ " instead of [" + inputResult.size() + ","
+ (inputResult.size() + optionResult.size()) + "]");
Class c = this.getClass();
Field f = null;
Object o;
// Mandatory input parameters
for (int i = 0; i < inputResult.size(); i++) {
o = inputList.get(i);
try {
f = c.getDeclaredField(inputResult.get(i));
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
System.out.println("Input #" + i + " " + inputResult.get(i)
+ " does not exist in " + c);
e.printStackTrace();
}
Class t = f.getType();
/*
* Class oc = o.getClass(); if (t.isPrimitive()) t =
* primitiveToObject(t);
*/
input.add(o);
try {
f.set(this, input.get(i));// Set the attributes of the
// inherited algorithm
} catch (IllegalArgumentException e) {
// If the type is incorrect
throw new InvalidTypeOfParameterException("Input type #" + i
+ " is not correct : " + o.getClass().getName()
+ " instead of " + t.getName());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// Optional input parameters
int nbOptions = Math.min(inputList.size() - inputResult.size(),
optionResult.size());
int indOptions = inputResult.size();
for (int i = 0; i < nbOptions; i++) {
o = inputList.get(i + indOptions);
// If null option, skip it
if (o == null) {
option.add(o);
continue;
}
try {
f = c.getDeclaredField(optionResult.get(i));
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
System.out.println("Option #" + i + " " + optionResult.get(i)
+ " does not exist in " + c);
e.printStackTrace();
}
Class t = f.getType();
option.add(o);
try {
f.set(this, option.get(i)); // Set the attributes of the
// inherited algorithm
} catch (IllegalArgumentException e) {
throw new InvalidTypeOfParameterException("Option type #" + i
+ " is not correct : " + o.getClass() + " instead of "
+ t);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
/**
* This method is used to return the list containing the outputs.
*
* @return the list containing the outputs
*/
public ArrayList getOutput() {
// parsing
ArrayList<String> parserResult = parser(outputs);
// clear output
output.clear();
Class c = this.getClass();
Field f = null;
Object o = null;
for (int i = 0; i < parserResult.size(); i++) {
try {
f = c.getDeclaredField(parserResult.get(i));
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
System.out.println("Output #" + i + " " + parserResult.get(i)
+ " does not exist in " + c);
e.printStackTrace();
}
try {
o = f.get(this);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
output.add(o);
}
return output;
}
/**
* This method is used to return an array containing the types of each input
* attribute.
*
* @return An array containing the types of each input attribute
*/
public Class[] getInputTypes() {
ArrayList<String> parserResult = parser(inputs);
Class[] tab = new Class[parserResult.size()];
Class c = this.getClass();
Field f = null;
for (int i = 0; i < parserResult.size(); i++) {
try {
f = c.getDeclaredField(parserResult.get(i));
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (f.getType() == int.class) {
tab[i] = Integer.class;
} else {
if (f.getType() == char.class) {
tab[i] = Character.class;
} else {
if (f.getType() == byte.class) {
tab[i] = Byte.class;
} else {
if (f.getType() == short.class) {
tab[i] = Short.class;
} else {
if (f.getType() == long.class) {
tab[i] = Long.class;
} else {
if (f.getType() == float.class) {
tab[i] = Float.class;
} else {
if (f.getType() == double.class) {
tab[i] = Double.class;
} else {
if (f.getType() == boolean.class) {
tab[i] = Boolean.class;
} else {
if (f.getType() == void.class) {
tab[i] = Void.class;
} else {
tab[i] = f.getType();
}
}
}
}
}
}
}
}
}
}
return tab;
}
/**
* This method is used to return an array containing the types of each
* option attribute.
*
* @return An array containing the types of each option attribute
*/
public Class[] getOptionTypes() {
ArrayList<String> parserResult = parser(options);
Class[] tab = new Class[parserResult.size()];
Class c = this.getClass();
Field f = null;
for (int i = 0; i < parserResult.size(); i++) {
try {
f = c.getDeclaredField(parserResult.get(i));
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (f.getType() == int.class) {
tab[i] = Integer.class;
} else {
if (f.getType() == char.class) {
tab[i] = Character.class;
} else {
if (f.getType() == byte.class) {
tab[i] = Byte.class;
} else {
if (f.getType() == short.class) {
tab[i] = Short.class;
} else {
if (f.getType() == long.class) {
tab[i] = Long.class;
} else {
if (f.getType() == float.class) {
tab[i] = Float.class;
} else {
if (f.getType() == double.class) {
tab[i] = Double.class;
} else {
if (f.getType() == boolean.class) {
tab[i] = Boolean.class;
} else {
if (f.getType() == void.class) {
tab[i] = Void.class;
} else {
if (f.getType() == int[].class) {
tab[i] = Integer[].class;
} else {
if (f.getType() == char[].class) {
tab[i] = Character[].class;
} else {
if (f.getType() == byte[].class) {
tab[i] = Byte[].class;
} else {
if (f.getType() == short[].class) {
tab[i] = Short[].class;
} else {
if (f.getType() == long[].class) {
tab[i] = Long[].class;
} else {
if (f
.getType() == float[].class) {
tab[i] = Float[].class;
} else {
if (f
.getType() == double[].class) {
tab[i] = Double[].class;
} else {
if (f
.getType() == boolean[].class) {
tab[i] = Boolean[].class;
} else {
tab[i] = f
.getType();
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
return tab;
}
/**
* This method is used to return an array containing the types of each
* output attribute.
*
* @return An array containing the types of each output attribute
*/
public Class[] getOutputTypes() {
ArrayList<String> parserResult = parser(outputs);
Class[] tab = new Class[parserResult.size()];
Class c = this.getClass();
Field f = null;
for (int i = 0; i < parserResult.size(); i++) {
try {
f = c.getDeclaredField(parserResult.get(i));
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (f.getType() == int.class) {
tab[i] = Integer.class;
} else {
if (f.getType() == char.class) {
tab[i] = Character.class;
} else {
if (f.getType() == byte.class) {
tab[i] = Byte.class;
} else {
if (f.getType() == short.class) {
tab[i] = Short.class;
} else {
if (f.getType() == long.class) {
tab[i] = Long.class;
} else {
if (f.getType() == float.class) {
tab[i] = Float.class;
} else {
if (f.getType() == double.class) {
tab[i] = Double.class;
} else {
if (f.getType() == boolean.class) {
tab[i] = Boolean.class;
} else {
if (f.getType() == void.class) {
tab[i] = Void.class;
} else {
if (f.getType() == int[].class) {
tab[i] = Integer[].class;
} else {
if (f.getType() == char[].class) {
tab[i] = Character[].class;
} else {
if (f.getType() == byte[].class) {
tab[i] = Byte[].class;
} else {
if (f.getType() == short[].class) {
tab[i] = Short[].class;
} else {
if (f.getType() == long[].class) {
tab[i] = Long[].class;
} else {
if (f
.getType() == float[].class) {
tab[i] = Float[].class;
} else {
if (f
.getType() == double[].class) {
tab[i] = Double[].class;
} else {
if (f
.getType() == boolean[].class) {
tab[i] = Boolean[].class;
} else {
tab[i] = f
.getType();
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
return tab;
}
/**
* This method is used to return the description of the extended algorithm.
*
* @return The description of the extended algorithm
*/
public String help() {
return help;
}
/**
* This method parse the input string containing the names of the inherited
* attributes.
*
* @param string
* input string containing the names of the inherited attributes.
* @return A list of string where each element is a names of an inherited
* attribute.
*/
private ArrayList<String> parser(String string) {
int beg = 0;
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < string.length(); i++) {
if (string.charAt(i) == ',') {
list.add(string.substring(beg, i));
beg = i + 1;
}
if (i == (string.length() - 1)) {
list.add(string.substring(beg, i + 1));
beg = i + 1;
}
}
return list;
}
/*
* private Class primitiveToObject(Class c) { if (c==Boolean.TYPE) return
* Boolean.class; else if (c==Byte.TYPE) return Byte.class; else if
* (c==Character.TYPE) return Character.class; else if (c==Short.TYPE)
* return Short.class; else if (c==Integer.TYPE) return Integer.class; else
* if (c==Long.TYPE) return Long.class; else if (c==Float.TYPE) return
* Float.class; else if (c==Double.TYPE) return Double.class; return null; }
*/
/**
* This method is used to return both the descrition of the algorithm and
* the attributes.
*/
public String toString() {
Class c = this.getClass();
String result = "" + c.toString() + ": " + help + "\n" + "inputs: "
+ inputs + "\n" + "options: " + options + "\n" + "outputs: "
+ outputs + "\n";
return result;
}
/**
* This method is used to return the list containing the input names.
*
* @return the list containing the input names.
*/
public ArrayList<String> getInputNames() {
return parser(inputs);
}
/**
* This method is used to return the list containing the option names.
*
* @return the list containing the option names.
*/
public ArrayList<String> getOptionNames() {
return parser(options);
}
/**
* This method is used to return the list containing the output names.
*
* @return the list containing the output names.
*/
public ArrayList<String> getOutputNames() {
return parser(outputs);
}
/**
* This method is used to return the result fo the inherited algorithm.
*
* @param parameters
* the parameters to process
*
* @return the result fo the inherited algorithm
* @throws InvalidTypeOfParameterException
* @throws AlgorithmException
* @throws InvalidNumberOfParametersException
*/
public Object process(Object... parameters)
throws InvalidTypeOfParameterException, AlgorithmException,
InvalidNumberOfParametersException {
ArrayList<Object> inputs = new ArrayList<Object>();
for (int i = 0; i < parameters.length; i++) {
inputs.add(parameters[i]);
}
this.setInput(inputs);
this.launch();
if (this.getOutput() == null || this.getOutput().isEmpty()) {
return null;
} else {
return (Object) this.getOutput().get(0);
}
}
/**
* This method is used to return An arraylist containing all the outputs of
* the inherited algorithm.
*
* @param parameters
* the parameters to process
*
* @return An arraylist containing all the outtputs of the inherited
* algorithm
* @throws InvalidTypeOfParameterException
* @throws AlgorithmException
* @throws InvalidNumberOfParametersException
*/
public ArrayList<Object> processAll(Object... parameters)
throws InvalidTypeOfParameterException, AlgorithmException,
InvalidNumberOfParametersException {
ArrayList<Object> inputs = new ArrayList<Object>();
for (int i = 0; i < parameters.length; i++) {
inputs.add(parameters[i]);
}
this.setInput(inputs);
this.launch();
if (this.getOutput() == null) {
return null;
} else {
return this.getOutput();
}
}
/**
* This method is used to return the number x element from the list of
* output of the inherited algorithm.
*
* @param x
* @param parameters
* the parameters to process
*
* @return An arraylist containing all the outputs of the inherited
* algorithm
* @throws InvalidTypeOfParameterException
* @throws AlgorithmException
* @throws InvalidNumberOfParametersException
*/
public Object processOne(Integer x, Object... parameters)
throws InvalidTypeOfParameterException, AlgorithmException,
InvalidNumberOfParametersException {
ArrayList<Object> inputs = new ArrayList<Object>();
for (int i = 0; i < parameters.length; i++) {
inputs.add(parameters[i]);
}
this.setInput(inputs);
this.launch();
if (this.getOutput() == null || x < 0 || x >= this.getOutput().size())
throw new InvalidNumberOfParametersException("output " + x
+ " does not exist");
return this.getOutput().get(x);
}
/**
* Build a default test case, i.e. perform algorithm and store
* inputs/outputs in files algorithmName.0.inputs and
* algorithmName.0.outputs
*
* @param parameters
* parameters of the algorithm
*/
public void buildTestCase(Object... parameters) {
buildSpecificTestCase(0, parameters);
}
/**
* Build a given test case, i.e. perform algorithm and store inputs/outputs
* in files algorithmName.testCaseID.inputs and
* algorithmName.testCaseID.outputs
*
* @param testID
* ID of the test case files to remove
* @param parameters
* parameters of the algorithm
*/
public void buildSpecificTestCase(int testID, Object... parameters) {
if (isTestCase(testID)) {
throw new PelicanException("Test case #"+testID+" has already been defined for "+getAlgorithmName());
}
// Récupération des paramètres d'entrée
ArrayList<Object> inputs = new ArrayList<Object>();
for (int i = 0; i < parameters.length; i++) {
inputs.add(parameters[i]);
}
// Traitement
this.setInput(inputs);
this.launch();
ArrayList<Object> outputs = this.getOutput();
// Création des noms de fichiers pour les entrées et sorties
String algoName = "tests/"+this.getAlgorithmName() + "." + testID;
String inputFile = algoName + INPUT_SUFFIX;
String outputFile = algoName + OUTPUT_SUFFIX;
// Sauvegarde des entrées et sorties
try {
ObjectOutputStream f = null;
// f = new ObjectOutputStream(new FileOutputStream(inputFile));
f = new ObjectOutputStream(new GZIPOutputStream(
new FileOutputStream(inputFile)));
f.writeObject(inputs);
f.close();
f = new ObjectOutputStream(new GZIPOutputStream(
new FileOutputStream(outputFile)));
f.writeObject(outputs);
f.close();
} catch (IOException ex) {
throw new AlgorithmException("file writing error with files: "
+ inputFile + " & " + outputFile);
}
}
/**
* Removes the default test case, i.e. files algorithmName.0.inputs
* and algorithmName.0.outputs
*/
public void removeTestCase() {
removeTestCase(0);
}
/**
* Removes a given test case, i.e. files
* algorithmName.testCaseID.inputs and
* algorithmName.testCaseID.outputs
*
* @param testID
* ID of the test case files to remove
*/
public void removeTestCase(int testID) {
// Création des noms de fichiers pour les entrées et sorties
String algoName = "tests/"+this.getAlgorithmName() + "." + testID;
String inputFile = algoName + INPUT_SUFFIX;
String outputFile = algoName + OUTPUT_SUFFIX;
// Suppression des fichiers
try {
File in = new File(inputFile);
in.delete();
File out = new File(outputFile);
out.delete();
} catch (SecurityException ex) {
throw new AlgorithmException("unable to remove files: " + inputFile
+ " & " + outputFile);
}
}
/**
* Loads inputs for a default test case, i.e. file
* algorithmName.0.inputs
*
* @return input parameters stored for this test case
*/
public ArrayList<Object> loadTestCaseInputs() {
return loadTestCaseInputs(0);
}
/**
* Loads outputs for a default test case, i.e. file
* algorithmName.0.outputs
*
* @return output parameters stored for this test case
*/
public ArrayList<Object> loadTestCaseOutputs() {
return loadTestCaseOutputs(0);
}
/**
* Loads inputs for a given test case, i.e. file
* algorithmName.testID.inputs
*
* @param testID
* ID of the test case file
* @return input parameters stored for this test case
*/
public ArrayList<Object> loadTestCaseInputs(int testID) {
ArrayList<Object> result = null;
String algoName = "tests/"+this.getAlgorithmName() + "." + testID;
String inputFile = algoName + INPUT_SUFFIX;
try {
ObjectInputStream f = null;
// f = new ObjectOutputStream(new FileOutputStream(inputFile));
f = new ObjectInputStream(new GZIPInputStream(new FileInputStream(
inputFile)));
result = (ArrayList<Object>) f.readObject();
f.close();
} catch (IOException ex) {
throw new AlgorithmException("file reading error with file: "
+ inputFile);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
/**
* Loads outputs for a given test case, i.e. file
* algorithmName.testID.outputs
*
* @param testID
* ID of the test case file
* @return output parameters stored for this test case
*/
public ArrayList<Object> loadTestCaseOutputs(int testID) {
ArrayList<Object> result = null;
String algoName = "tests/"+this.getAlgorithmName() + "." + testID;
String inputFile = algoName + OUTPUT_SUFFIX;
try {
ObjectInputStream f = null;
// f = new ObjectOutputStream(new FileOutputStream(inputFile));
f = new ObjectInputStream(new GZIPInputStream(new FileInputStream(
inputFile)));
result = (ArrayList<Object>) f.readObject();
f.close();
} catch (IOException ex) {
throw new AlgorithmException("file reading error with file: "
+ inputFile);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
/**
* Removes all test cases, i.e. files
* algorithmName.testCaseID.inputs and
* algorithmName.testCaseID.outputs
*/
public void removeAllTestCases() {
// Build list of files to remove
File dir = new File("tests");
File[] files = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.matches(getAlgorithmName().replace(".", "\\.")
+ "\\.\\d+" + "(" + INPUT_SUFFIX + "|" + OUTPUT_SUFFIX
+ ")");
}
});
// Remove all files
for (File f : files)
try {
f.delete();
} catch (SecurityException ex) {
throw new AlgorithmException("unable to remove file: " + f);
}
}
/**
* Build the default filename for algorithm data
*
* @return the default filename
*/
private String getAlgorithmName() {
String algoName = this.getClass().getName();
String token = "algorithms.";
algoName = algoName.substring(algoName.indexOf(token) + token.length());
return algoName;
}
/**
* Performs a default test case using stored input/output test case data
*/
public void launchTestCase() {
launchTestCase(0);
}
/**
* Performs a given test case using stored input/output test case data
*
* @param testID
* ID of the test case file
*/
public void launchTestCase(int testID) {
setInput(loadTestCaseInputs(testID));
launch();
assertObjects(getOutput(), loadTestCaseOutputs(testID));
}
/**
* Recursive assertion method used to compare objects
* @param obj1 first object to be compared
* @param obj2 second object to be compared
*/
private void assertObjects(Object obj1, Object obj2) {
// Check object types
Assert.assertEquals(getAlgorithmName(), obj1.getClass(), obj2.getClass());
// Modify type if AbstractCollection
if (obj1 instanceof AbstractCollection) {
obj1 = ((AbstractCollection) obj1).toArray();
obj2 = ((AbstractCollection) obj2).toArray();
}
// Recursive check if arrays
if (obj1.getClass().isArray()) {
Assert.assertEquals(Array.getLength(obj1),Array.getLength(obj2));
for (int i=0;i<Array.getLength(obj1);i++) {
assertObjects(Array.get(obj1, i),Array.get(obj2, i));
}
}
// Standard check otherwise
else
Assert.assertEquals(getAlgorithmName(), obj1,obj2);
}
/**
* Checks if input/output test case data have been stored for a default test
* case
*
* @return true if input/output test case data exist, false otherwise
*/
public boolean isTestCase() {
return isTestCase(0);
}
/**
* Checks if input/output test case data have been stored for a given test
* case
*
* @param testID
* ID of the test case file
* @return true if input/output test case data exist, false otherwise
*/
public boolean isTestCase(int testID) {
// Création des noms de fichiers pour les entrées et sorties
String algoName = "tests/"+this.getAlgorithmName() + "." + testID;
String inputFile = algoName + INPUT_SUFFIX;
String outputFile = algoName + OUTPUT_SUFFIX;
// Existence des fichiers
boolean exists = true;
try {
File in = new File(inputFile);
File out = new File(outputFile);
exists = in.exists() && out.exists();
} catch (SecurityException ex) {
throw new AlgorithmException("unable to check files: " + inputFile
+ " & " + outputFile);
}
return exists;
}
}