/*
* Copyright (C) 2015 Artificial Intelligence
* Laboratory @ University of Udine.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package it.uniud.ailab.dcore.annotation;
import it.uniud.ailab.dcore.utils.Either;
import it.uniud.ailab.dcore.utils.Either.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* An abstract annotation. It forces child annotators to identify themselves by
* calling the one-argument constructor, where they have to pass their
* identifier as a string. <br/>
*
* To add values to an annotation one can create its own fields, but it is
* <b>strongly</b>
* advised to use the provided {@code addNumber()} and {@code addString()}
* methods. This way, these values can be automatically retrieved by other
* classes even if they don't know what is the <b>actual</b> annotation they're
* dealing with.<br/>
*
* To retrieve the stored values simply keep track of the order in which they're
* added to the array. Since all the values should be added by the constructor
* of the concrete annotation this should not really be a problem. Hint: if
* you're adding a value in the values array outside of the constructor, you're
* probably doing it wrong!<br/>
*
* It's obviously possible to mix both approaches. For example, the
* {@link it.uniud.ailab.dcore.annotation.annotations.TextAnnotation} class uses
* both the values array and a custom property. In this case, it's not possible
* to retrieve the custom property automatically, but all the "output-worthy"
* information is stored in the values array.<br/>
*
* Identifiers should be set as {@code public static final} fields in each
* Annotator class, such as in
* {@link it.uniud.ailab.dcore.annotation.annotators.GenericEvaluatorAnnotator}.
*
* @author Marco Basaldella
*/
public abstract class Annotation {
private final String[] FORBIDDEN_NAMES
= {"annotator", "name", "id", "identifier"};
private final char[] FORBIDDEN_CHARS
= {'$','\n','\t','\r'};
/**
* The identifier of the annotator that produced the annotation.
*/
protected final String annotator;
/**
* The values of the annotation.
*/
private final List<Either<String, Number>> values;
/**
* Creates an annotation with a mandatory annotator name. The annotator
* Name
*
* @param annotator
*/
protected Annotation(String annotator) {
// Check for forbidden annotator names
if (Arrays.asList(FORBIDDEN_NAMES).contains(
annotator.trim().toLowerCase())) {
throw new UnsupportedOperationException(
"Forbidden annotator name: " + annotator);
}
// Check for fobidden chars in the annotator name
for (char c : FORBIDDEN_CHARS)
if (annotator.indexOf(c) >= 0)
throw new UnsupportedOperationException(
"Forbidden annotator name: " + annotator);
this.annotator = annotator;
values = new ArrayList<>();
}
/**
* Gets the identifier of the annotator that produced the annotation.
*
* @return the identifier of the annotator.
*/
public String getAnnotator() {
return annotator;
}
/**
* Adds a string to the values list.
*
* @param s the annotation to add
*/
public void addString(String s) {
values.add(new Left<>(s));
}
/**
* Adds a numeric to the values list.
*
* @param n the annotation to add.
*/
public void addNumber(Number n) {
values.add(new Right<>(n));
}
/**
* Checks if the value in position {@code i} is a String
*
* @param i the position of the value to check
* @return true if it's a String; false otherwise
*/
public boolean isString(int i) {
return values.get(i).isLeft();
}
/**
* Checks if the value in position {@code i} is a Number
*
* @param i the position of the value to check
* @return true if it's a Number; false otherwise
*/
public boolean isNumber(int i) {
return values.get(i).isRight();
}
/**
* Gets the string value in position {@code i} of the values array.
*
* @param i the position of the annotation to retrieve
* @return the requested annotation
*/
public String getStringAt(int i) {
if (isString(i)) {
return values.get(i).getLeft();
} else {
throw new UnsupportedOperationException(
"Extracting String from a Number field.");
}
}
/**
* Gets the numeric value in position {@code i} of the values array.
*
* @param i the position of the annotation to retrieve
* @return the requested annotation
*/
public Number getNumberAt(int i) {
if (isNumber(i)) {
return values.get(i).getRight();
} else {
throw new UnsupportedOperationException(
"Extracting Number from a String field.");
}
}
/**
* Gets the value in position {@code i} of the values array, leaving to the
* consumer the duty to detect if it's a String or a Number.
*
* @param i the position of the annotation to retrieve
* @return the requested annotation
*/
public Either<String, Number> getValueAt(int i) {
if (isString(i)) {
return values.get(i);
} else {
return values.get(i);
}
}
/**
* Get the number of values contained in the Annotation.
*
* @return the number of values contained in the Annotation.
*/
public int size() {
return values.size();
}
@Override
public String toString() {
String output = annotator + ": ";
return output.substring(0, output.length() - 1);
}
}