/**
*
*/
package it.univr.di.labeledvalue;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import it.univr.di.Debug;
/**
* Represents an immutable Labeled Lower Case value.
*
* @implSpec
* For speeding up the DC checking algorithms, such class is very basic.
* No integrity controls are done.
* Such class admit an empty object (where all field are empty).
* For checking if an object is empty, use isEmpty().
*
* Be aware: the internal node name is represented as {@link ALabel}!
* Since lower-case value are few in any CSTNU, this imolementation does not provide a cache of created object.
* @author posenato
*/
public class LabeledLowerCaseValue {
/**
* Logger.
*/
private static final Logger LOG = Logger.getLogger("LabeledLowerCaseValue");
/**
* A constant empty label to represent an empty label that cannot be modified.
*/
public static final LabeledLowerCaseValue emptyLabeledLowerCaseValue = new LabeledLowerCaseValue();
/**
* Parses a string representing a labeled lower-case value and returns an object containing the labeled values represented by the string.
* The format of the string is given by the method {@link #toString()}:\{{(〈label〉, 〈Alabel〉, 〈value〉) }*\}
* It also parse the old format: \{{(〈Alabel〉, 〈value〉, 〈label〉) }*\}
*
* @param arg a {@link java.lang.String} object.
* @param alphabet the alphabet to use for building a new labeled lower-case value. If null, a new alphabet is generated and insert into the created labeled
* value.
* @return a LabeledLowerCaseValue object if arg represents a valid labeled value, null otherwise.
*/
public static LabeledLowerCaseValue parse(String arg, ALabelAlphabet alphabet) {
// final Pattern splitterNode = Pattern.compile("〈|; ");
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Begin parse: " + arg);
}
}
if ((arg == null) || (arg.length() < 2))
return null;
if (arg.equals("{}"))
return emptyLabeledLowerCaseValue;
if (!LabeledALabelIntTreeMap.patternlabelCharsRE.matcher(arg).matches())
return null;
arg = arg.replaceAll("[{}]", "");
// arg = arg.substring(1, arg.length() - 2);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Before split: '" + arg + "'");
}
}
final Pattern splitterEntry = Pattern.compile("\\)|\\(");
final String[] entryThreesome = splitterEntry.split(arg);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("EntryThreesome: " + Arrays.toString(entryThreesome));
}
}
final Pattern splitterTriple = Pattern.compile(", ");
int j;
String labelStr, aLabelStr, valueStr;
// THERE IS ONLY ONE ENTRY
if (alphabet == null) {
alphabet = new ALabelAlphabet();
}
for (final String s : entryThreesome) {
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("s: '" + s + "'");
}
}
if (s.length() > 1) {// s can be empty or a space.
final String[] triple = splitterTriple.split(s);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("triple: " + Arrays.toString(triple));
}
}
Label l = Label.parse(triple[2]);
if (l == null) {
// probably it is the old format
labelStr = triple[0];
aLabelStr = triple[1];
valueStr = triple[2];
} else {
// new format
aLabelStr = triple[0];
valueStr = triple[1];
labelStr = triple[2];
}
if (l == null)
l = Label.parse(labelStr);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Label: " + l);
}
}
if (valueStr.equals("-" + Constants.INFINITY_SYMBOLstring))
j = Constants.INT_NEG_INFINITE;
else
j = Integer.parseInt(valueStr);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("Value: " + j);
}
} // LabeledNode is represented as " 〈; {}; Obs: null〉 "
// final String nodePart = labLitInt[1];//splitterNode.split(labLitInt[1]);
final ALabel node = new ALabel(aLabelStr, alphabet);
if (Debug.ON) {
if (LOG.isLoggable(Level.FINEST)) {
LOG.finest("LabeledNode: " + node);
}
}
return new LabeledLowerCaseValue(node, j, l);
}
}
return emptyLabeledLowerCaseValue;
}
/**
* Creates a lower-case value.
*
* @param nodeName not null node name
* @param value not null value
* @param label not null label
* @return a new LabeledLowerCaseValue object
*/
static public LabeledLowerCaseValue create(ALabel nodeName, int value, Label label) {
if (nodeName == null || value == Constants.INT_NULL || label == null)
return emptyLabeledLowerCaseValue;
if (nodeName.size() > 1)
throw new IllegalArgumentException("Node name label must contain only one name!");
return new LabeledLowerCaseValue(nodeName, value, label);
}
/**
* Copy constructor.
* The new object is distinct from input.
* No null check is done!
*
* @param input
* @return a new LabeledLowerCaseValue object with equals fields of input
*/
static public LabeledLowerCaseValue create(LabeledLowerCaseValue input) {
if (input == null || input.isEmpty())
return emptyLabeledLowerCaseValue;
return new LabeledLowerCaseValue(ALabel.clone(input.nodeName), input.value, input.label);
}
/**
*
*/
private Label label;
/**
* Even if this field could be just a ALetter, it is an ALabel because the comparison between ALabels is faster than ALetter and ALabel.
*/
private ALabel nodeName;
/**
*
*/
private int value;
/**
* cached hash code
*/
private int hashCode;
/**
* Creates an empty lower-case value.
*
* @implSpec
* Externally, users have to use {@link #emptyLabeledLowerCaseValue} for having an empty object.
*/
private LabeledLowerCaseValue() {
this.label = null;
this.nodeName = null;
this.value = Constants.INT_NULL;
}
/**
* @param nodeName1 a not null node name
* @param value1 a value different from {@link Constants#INT_NULL}
* @param label1 a non null label
*/
private LabeledLowerCaseValue(ALabel nodeName1, int value1, Label label1) {
if (nodeName1 == null || value1 == Constants.INT_NULL || label1 == null)
return;
if (nodeName1.size() > 1)
throw new IllegalArgumentException("Node name label must contain only one name!");
this.label = label1;
this.nodeName = nodeName1;
this.value = value1;
}
/**
* @return the label
*/
public Label getLabel() {
return this.label;
}
/**
* @return the node name
*/
public ALabel getNodeName() {
return this.nodeName;
}
/**
* @return the value
*/
public int getValue() {
return this.value;
}
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof LabeledLowerCaseValue))
return false;
LabeledLowerCaseValue v = (LabeledLowerCaseValue) o;
return this.value == v.value && this.label.equals(v.label) && this.nodeName.equals(v.nodeName);
}
@Override
public int hashCode() {
int result = this.hashCode;
if (result == 0) {
result = (isEmpty()) ? 0 : (this.value * 31 + this.label.hashCode()) * 31 + this.nodeName.hashCode();
this.hashCode = result;
}
return result;
}
/**
* @return true if the object is empty
* @implSpec it is empty when at least one of its fields is null
*/
public boolean isEmpty() {
return (this.nodeName == null || this.value == Constants.INT_NULL || this.label == null);
}
@Override
public String toString() {
return this.toString(false);
}
/**
* @param nodeN
* @param v
* @param l
* @param lower true if the node name has to be written lower case
* @return the string representation of this lower-case value.
*/
public static String entryAsString(ALabel nodeN, int v, Label l, boolean lower) {
final StringBuffer s = new StringBuffer("{");// this is necessary for saving the value in a file in the old format
s.append(Constants.OPEN_PAIR);
s.append((lower) ? nodeN.toLowerCase() : nodeN);
s.append(", ");
s.append(Constants.formatInt(v));
s.append(", ");
s.append(l);
s.append(Constants.CLOSE_PAIR);
s.append(' ');
s.append("}");
return s.toString();
}
/**
* @param lower true if the node name has to be written lower case
* @return the string representation of this lower-case value
*/
public String toString(boolean lower) {
if (this.isEmpty())
return "{}";
return entryAsString(this.nodeName, this.value, this.label, lower);
}
}