package it.univr.di.labeledvalue; /** * An immutable literal. *
* A literal is a char that can be preceded by the symbol {@value it.univr.di.labeledvalue.Constants#NOT}, negated literal, or by the symbol * {@value it.univr.di.labeledvalue.Constants#UNKNOWN}, 'unknown' literal. *
* While the semantics of a literal and its negation is the standard one, the semantics for unknown literal is particular of the CSTN/CSTNU application.
* An unknown literal, as '¿p' for example, is true if the value of proposition letter 'p' is not assigned yet. False otherwise.
*
* Therefore, if a state is characterized by the proposition '¿p', it means that the state is valid till the value of proposition letter 'p' is unknown. * In the instant the value of 'p' is set, '¿p' became false and the associated state is not more valid. *
* A literal object is immutable and must have a propositional letter. *
* Lastly, for efficiency reasons, this class allows to represent literal using at most {@link Label#NUMBER_OF_POSSIBLE_PROPOSITIONS} propositions in the range
* {@link #PROPOSITION_ARRAY}.
* {@link Label#NUMBER_OF_POSSIBLE_PROPOSITIONS} is given by the fact that {@link Label} represents propositional labels using integer (32 bits), so labels with
* at most 32 different propositions.
*
* @author Roberto Posenato
* @version $Id: $Id
*/
public class Literal implements Comparable
* The complement of a straight literal is the negated one.
* Therefore I decided to simplify the representation using 4 constants value: {@link #ABSENT}, {@link #STRAIGHT}, {@link #NEGATED}, and {@link #UNKNONW}.
* The char corresponding to each such constant is exploit in the class (to make them more efficient).
* So, don't change them without revising all the class.
* {@link #ABSENT} is useful only for internal methods. It is not admitted for defining a literal.
*/
public static final char ABSENT = '\u0000',
STRAIGHT = '\u0001',
NEGATED = Constants.NOT,
UNKNONW = Constants.UNKNOWN;
/**
* Literal object cache
*/
private static final Literal[] CREATED_LITERAL = new Literal[Label.NUMBER_OF_POSSIBLE_PROPOSITIONS * 3];
/**
* List of possible proposition managed by this class.
* Such list is made concatenating 2 blocks: a-z, and A-F.
* If such blocks are changed, please revise {@link #check(char)} and {@link #index(char)} methods because it exploits the bounds of such blocks.
* The length of this array cannot be modified without revising all this class code and {@link Label} class.
*
* @see #PROPOSITIONS
*/
// 3 blocks: a-z, A-Z, α-μ.
public static final char[] PROPOSITION_ARRAY = {
// 0 1 2 3 4 5 6 7 8 9
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
'E', 'F'
// 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
// 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ'
};
/**
* R.E. representation of allowed propositions.
*/
public static final String PROPOSITIONS = "a-zA-F";// "A-Za-z0-9α-μ";
/**
* R.E. representation of {@link #PROPOSITION_ARRAY}
*/
public static final String PROPOSITION_RANGE = "[" + PROPOSITIONS + "]";
/**
* @param state1 a possible state of a literal. No integrity check is done
* @param state2 a possible state of a literal. No integrity check is done
* @return true if state1 and state2 are complement. False otherwise
* @see #STRAIGHT
* @see #NEGATED
*/
static final boolean areComplement(char state1, char state2) {
return (state1 == STRAIGHT && state2 == NEGATED) || (state1 == NEGATED && state2 == STRAIGHT);
}
/**
* @param i a positive value smaller than {@value Label#NUMBER_OF_POSSIBLE_PROPOSITIONS}.
* @return char at position i in {@link Literal#PROPOSITION_ARRAY}
* @impleSpec No parameter integrity-check is done
*/
static final char charValue(final int i) {
return PROPOSITION_ARRAY[i];
}
/**
* @param c the char to check
* @return true if the char represents a valid literal identifier
*/
public static final boolean check(final char c) {
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'F');// 'Z') || ('α' <= c && c <= 'μ');
}
/**
* Returns the positive literal of {@code v}.
*
* @param v a char in the range {@link #PROPOSITION_RANGE}
* @return the straight literal of proposition v
*/
public static Literal valueOf(final char v) {
return valueOf(v, STRAIGHT);
}
/**
* Return the literal having the given {@code state} of {@code v}.
*
* @param v the proposition letter
* @param state one of possible state of a literal {@link #NEGATED} or {@link #STRAIGHT} o {@link #UNKNONW}
* @return a literal with name {@code v} and state {@code state}, null if the char is not valid or state if {@link #ABSENT}.
*/
public static Literal valueOf(final char v, char state) {
if (!Literal.check(v) || state == ABSENT)
return null;
int hc = hashCode(v, state);
Literal l = CREATED_LITERAL[hc];
if (l == null) {
l = new Literal(v, state);
CREATED_LITERAL[hc] = l;
}
return l;
}
/**
* Returns a new literal having same proposition of {@code v} but with state given by {@code state}.
*
* @param v a non null literal v
* @param state one of possible state of a literal: {@link #NEGATED} or {@link #STRAIGHT} o {@link #UNKNONW}
* @return a new literal having same proposition of {@code v} but with state given by {@code state}.
*/
public static Literal valueOf(final Literal v, char state) {
if (state == ABSENT || v == null)
throw new IllegalArgumentException("The state or the input literal is not valid!");
int hc = -1;
Literal l = CREATED_LITERAL[hc = hashCode(v.name, state)];
if (l == null) {
l = new Literal(v.name, state);
CREATED_LITERAL[hc] = l;
}
return l;
}
/**
* Returns the ordinal associate to {@code state}.
*
* @implNote Since it is necessary to order literals, it is necessary to fix an order among possible state of a literal.
* The order implemented by this method allows to be independent by the alphabetic order implied by the corresponding char of the state.}
* @param state One of the following value: {@value #NEGATED}, {@value #STRAIGHT}, {@value #UNKNONW}
* @return the ordinal associated to a proper state, a negative integer if the state is not recognized
*/
static final byte getStateOrdinal(char state) {
switch (state) {
case STRAIGHT:
return 1;
case NEGATED:
return 2;
case UNKNONW:
return 3;
case ABSENT:
return 0;
default:
return -1;
}
}
/**
* Hash code for a literal given as char {@code c} and state {@code state}.
*
* @param c
* @param state one of possible state of a literal {@link #NEGATED} or {@link #STRAIGHT} o {@link #UNKNONW}. No integrity check is done.
* @return an integer that is surely unique when 'a'<=c<='z'.
*/
static final int hashCode(char c, char state) {
return index(c) * 3 + getStateOrdinal(state) - 1;// -1 because ABSENT is not admissible.
}
/**
* @param c
* @return the index of the given proposition {@code c} in {@link #PROPOSITION_ARRAY} if it is a proposition, a negative integer otherwise.
*/
static final byte index(final char c) {
if ('a' <= c && c <= 'z')
return (byte) (c - 'a');
if ('A' <= c && c <= 'F') // if ('A' <= c && c <= 'Z')
return (byte) ((c - 'A') + 26);// 26 is 'A' position in PROPOSITION_ARRAY
// if ('α' <= c && c <= 'μ')
// return (byte) ((c - 'α') + 52);// 26 is 'α' position in PROPOSITION_ARRAY
return -1;
}
/**
* Parses the string {@code s} returning the literal represented.
*
* @param s It can be a single char ({@value #PROPOSITION_ARRAY}) or one of characters [{@value Constants#NOT} {@value Constants#UNKNOWN}]
* followed by a char of {@value #PROPOSITION_ARRAY}. No spaces are allowed
* @return the literal represented by {@code s} if {@code s} is a valid representation of a literal, null otherwise
*/
public static final Literal parse(final String s) {
int len;
char p, state;
if (s == null || (len = s.length()) > 2)
return null;
if (len == 1) {
p = s.charAt(0);
if (!Literal.check(p))
return null;
final Literal l = Literal.valueOf(p, STRAIGHT);
return l;
}
state = s.charAt(0);
p = s.charAt(1);
if (!Literal.check(p))
return null;
if (state == Constants.NOT) {
final Literal l = Literal.valueOf(p, NEGATED);
return l;
}
if (state == Constants.UNKNOWN) {
final Literal l = Literal.valueOf(p, UNKNONW);
return l;
}
return null;
}
/**
* @param state
* @return the string representation of {@code state}
*/
static final String stateAsString(char state) {
if (state <= STRAIGHT)
return "";
return String.valueOf(state);
}
/**
* @param propositionIndex
* @param state one of possible state of a literal {@link #NEGATED} or {@link #STRAIGHT} o {@link #UNKNONW}
* @return the char-array representation of a literal identified by its index and state parameter. If state is not correct, an empty array is returned
* @implSpec No parameter integrity-check is done
*/
static final char[] toChars(int propositionIndex, char state) {
// Exploits the state fixed order.
if (state > STRAIGHT)
return new char[] { state, Literal.charValue(propositionIndex) };
if (state == STRAIGHT)
return new char[] { Literal.charValue(propositionIndex) };
return new char[0];
}
/**
* Hash code cache.
*/
private int hashCodeCached;
/**
* Immutable propositional letter.
*/
private char name;
/**
* Immutable state.
*/
private char state;
/**
* Makes the positive literal of {@code v}.
* This class is immutable. Use {@link #valueOf(char)}.
*
* @param v a char
*/
private Literal(final char v) {
this(v, STRAIGHT);
}
/**
* Makes a literal using {@code v} and {@code state}.
* This class is immutable, use {@link #valueOf(char, char)}
*
* @param v the proposition letter
* @param state1 one of possible state of a literal {@link #NEGATED} or {@link #STRAIGHT} o {@link #UNKNONW}
*/
private Literal(final char v, final char state1) {
if (!Literal.check(v))
throw new IllegalArgumentException("The char is not an admissible proposition!");
if (getStateOrdinal(state1) < 0)
throw new IllegalArgumentException("The state is not an admissible one!");
this.name = v;
this.state = state1;
}
/**
* @implSpec When the two literals have the same name, it returns a different value than 0 if this and {@code o} have different state.
*/
@Override
public int compareTo(final Literal o) {
if (this.name < o.name)
return -1;
else if (this.name > o.name)
return 1;
// Since compareTo has to be consistent with equals, when the two names are equal,
// it returns a different value than 0 if the two literals have different state.
return getStateOrdinal(this.state) - getStateOrdinal(o.state);
}
@Override
public final boolean equals(final Object o) {
if (o == this)
return true;
if (!(o instanceof Literal))
return false;
final Literal l = (Literal) o;
return this.name == l.name && this.state == l.state;
}
/**
* Returns the complement of this.
*
* The complement of a negated literal is the straight one.
* The complement of a unknown literal is null (an empty literal is not possible).
*
* @return a new literal that it is the negate of this. null if it is request the complement of unknown literal
*/
public final Literal getComplement() {
if (this.state == UNKNONW || this.state == ABSENT)
return null;
return valueOf(this, (this.isNegated()) ? STRAIGHT : NEGATED);
}
/**
* Getter for the field name
.
*
* @return the propositional letter associated to this
*/
public final char getName() {
return this.name;
}
/**
* Returns a literal that is the negated of this.
*
* @return a new literal with the same name and state negated
*/
public final Literal getNegated() {
return valueOf(this, NEGATED);
}
/**
* Getter for the field state
.
*
* @return the state
*/
public final char getState() {
return this.state;
}
/**
* Returns a literal that is the straight literal of this.
*
* @return a new literal with the same name and state straight
*/
public final Literal getStraight() {
return valueOf(this, STRAIGHT);
}
/**
* Returns a literal that is the unknown literal of this.
*
* @return a new literal with the same name and state unknown
*/
public final Literal getUnknown() {
return valueOf(this, UNKNONW);
}
@Override
public final int hashCode() {
if (this.hashCodeCached == 0) {
this.hashCodeCached = Literal.hashCode(this.name, this.state);
}
return this.hashCodeCached;
}
/**
* @param l
* @return true if it is a complement literal of the given one
*/
public final boolean isComplement(Literal l) {
if (l == null)
return false;
return areComplement(this.state, l.state);
}
/**
* @return true if it is a negated literal
*/
public final boolean isNegated() {
return this.state == NEGATED;
}
/**
* @return true if it is a straight literal
*/
public final boolean isStraight() {
return this.state == STRAIGHT;
}
/**
* @return true if it is a literal in the unknown state
*/
public final boolean isUnknown() {
return this.state == UNKNONW;
}
/**
* @return the string representation of this. If the literal is a negated one, the propositional letter is prefixed by {@link Constants#NOT}.
* If the literal is an unknown one, the propositional letter is prefixed by {@link Constants#UNKNOWN}
*/
@Override
public final String toString() {
return stateAsString(this.state) + this.name;
}
}