//----------------------------------------------------------------------------//
// //
// C o n s t a n t //
// //
//----------------------------------------------------------------------------//
// <editor-fold defaultstate="collapsed" desc="hdr"> //
// Copyright © Hervé Bitteur and others 2000-2013. All rights reserved. //
// This software is released under the GNU General Public License. //
// Goto http://kenai.com/projects/audiveris to report bugs or suggestions. //
//----------------------------------------------------------------------------//
// </editor-fold>
package omr.constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import omr.util.DoubleValue;
import net.jcip.annotations.ThreadSafe;
import java.util.concurrent.atomic.AtomicReference;
/**
* This abstract class handles the mapping between one application
* variable and a property name and value.
* It is meant essentially to handle any kind of symbolic constant, whose value
* may have to be tuned and saved for future runs of the application.
*
* <p>Please refer to {@link ConstantManager} for a detailed explanation on how
* the current value of any given Constant is determined at run-time.
*
* <p> The class {@code Constant} is not meant to be used directly (it is
* abstract), but rather through any of its subclasses:
*
* <ul> <li> {@link Constant.Angle} </li> <li> {@link Constant.Boolean} </li>
* <li> {@link Constant.Color} </li> <li> {@link Constant.Double} </li> <li>
* {@link Constant.Integer} </li> <li> {@link Constant.Ratio} </li>
* <li> {@link Constant.String} </li>
* <li>and others...</li></ul> </p>
*
* @author Hervé Bitteur
*/
@ThreadSafe
public abstract class Constant
{
//~ Static fields/initializers ---------------------------------------------
/** Usual logger utility */
private static final Logger logger = LoggerFactory.getLogger(Constant.class);
//~ Instance fields --------------------------------------------------------
//
// Data assigned at construction time
//-----------------------------------
/** Unit (if relevant) used by the quantity measured. */
private final java.lang.String quantityUnit;
/** Source-provided value to be used if needed. */
private final java.lang.String sourceString;
/** Semantic. */
private final java.lang.String description;
// Data assigned at ConstantSet initMap time
//------------------------------------------
/** Name of the Constant. */
private volatile java.lang.String name;
/** Fully qualified Constant name. (unit.name) */
private volatile java.lang.String qualifiedName;
// Data modified at any time
//--------------------------
/** Initial Value (used for reset). Assigned once */
private java.lang.String initialString;
/** Current data. */
private AtomicReference<Tuple> tuple = new AtomicReference<>();
//~ Constructors -----------------------------------------------------------
//
//----------//
// Constant //
//----------//
/**
* Creates a constant instance, while providing a default value,
* in case the external property is not yet defined.
*
* @param quantityUnit Unit used as base for measure, if relevant
* @param sourceString Source value, expressed by a string literal which
* cannot be null
* @param description A quick description of the purpose of this constant
*/
protected Constant (java.lang.String quantityUnit,
java.lang.String sourceString,
java.lang.String description)
{
if (sourceString == null) {
logger.warn(
"*** Constant with no sourceString. Description: {}",
description);
throw new IllegalArgumentException(
"Any constant must have a source-provided String");
}
this.quantityUnit = quantityUnit;
this.sourceString = sourceString;
this.description = description;
// System.out.println(
// Thread.currentThread().getName() + ": " + "-- Creating Constant: " +
// description);
}
//~ Methods ----------------------------------------------------------------
//
//----------//
// setValue //
//----------//
/**
* Modify the current value of the constant.
* This abstract method is actually defined in each subclass, to enforce
* validation of the provided string with respect to the target constant type.
*
* @param string the new value, as a string to be checked
*/
public abstract void setValue (java.lang.String string);
//------------------//
// getCurrentString //
//------------------//
/**
* Get the current value, as a String type.
*
* @return the String view of the value
*/
public java.lang.String getCurrentString ()
{
return getTuple().currentString;
}
//----------------//
// getDescription //
//----------------//
/**
* Get the description sentence recorded with the constant
*
* @return the description sentence as a string
*/
public java.lang.String getDescription ()
{
return description;
}
//---------//
// getName //
//---------//
/**
* Report the name of the constant
*
* @return the constant name
*/
public java.lang.String getName ()
{
return name;
}
//------------------//
// getQualifiedName //
//------------------//
/**
* Report the qualified name of the constant
*
* @return the constant qualified name
*/
public java.lang.String getQualifiedName ()
{
return qualifiedName;
}
//-----------------//
// getQuantityUnit //
//-----------------//
/**
* Report the unit, if any, used as base of quantity measure
*
* @return the quantity unit, if any
*/
public java.lang.String getQuantityUnit ()
{
return quantityUnit;
}
//------------------//
// getShortTypeName //
//------------------//
/**
* Report the very last part of the type name of the constant
*
* @return the type name (last part)
*/
public java.lang.String getShortTypeName ()
{
final java.lang.String typeName = getClass().getName();
final int separator = Math.max(
typeName.lastIndexOf('$'),
typeName.lastIndexOf('.'));
if (separator != -1) {
return typeName.substring(separator + 1);
} else {
return typeName;
}
}
//-----------------//
// getSourceString //
//-----------------//
/**
* Report the constant source string
*
* @return the source string
*/
public java.lang.String getSourceString ()
{
return sourceString;
}
//------------//
// isModified //
//------------//
/**
* Checks whether the current value is different from the original one.
* NOTA_BENE: The test is made on string literal, which may result in false
* modification signals, simply because the string for example contains an
* additional space
*
* @return The modification status
*/
public boolean isModified ()
{
return !getCurrentString().equals(initialString);
}
//---------------//
// isSourceValue //
//---------------//
/**
* Report whether the current constant value is the source one.
* (not altered by either properties read from disk, of value changed later)
*
* @return true if still the source value, false otherwise
*/
public boolean isSourceValue ()
{
return getCurrentString().equals(sourceString);
}
//--------//
// remove //
//--------//
/**
* Remove a given constant from memory
*/
public void remove ()
{
ConstantManager.getInstance().removeConstant(this);
}
//-------//
// reset //
//-------//
/**
* Forget any modification made, and reset to the source value.
*/
public void reset ()
{
setTuple(sourceString, decode(sourceString));
}
//---------//
// setUnit //
//---------//
/**
* Allows to record the unit and name of the constant.
*
* @param unit the unit (class name) this constant belongs to
* @param name the constant name
*/
public void setUnitAndName (java.lang.String unit,
java.lang.String name)
{
// System.out.println(
// Thread.currentThread().getName() + ": " + "Assigning unit:" + unit +
// " name:" + name);
this.name = name;
final java.lang.String qName = (unit != null) ? (unit + "." + name) : name;
// We can now try to register that constant
try {
java.lang.String prop = ConstantManager.getInstance().addConstant(
qName, this);
// Now we can assign a first current value
if (prop != null) {
// Use property value
setTuple(prop, decode(prop));
} else {
// Use source value
///logger.info("setUnitAndName. unit:" + unit + " name:" + name);
setTuple(sourceString, decode(sourceString));
}
// Very last thing
qualifiedName = qName;
// System.out.println(
// Thread.currentThread().getName() + ": " + "Done unit:" + unit +
// " name:" + name);
} catch (Exception ex) {
logger.warn("Error registering constant {}", qName);
ex.printStackTrace();
}
}
//------------------//
// toDetailedString //
//------------------//
/**
* Report detailed data about this constant
*
* @return data meant for end user
*/
public java.lang.String toDetailedString ()
{
StringBuilder sb = new StringBuilder(getQualifiedName());
sb.append(" (").append(getCurrentString()).append(")");
sb.append(" \"").append(getDescription()).append("\"");
return sb.toString();
}
//----------//
// toString //
//----------//
/**
* Used by UnitTreeTable to display the name of the constant,
* so only the unqualified name is returned.
*
* @return the (unqualified) constant name
*/
@Override
public java.lang.String toString ()
{
return (name != null) ? name : "*no name*";
}
//--------//
// decode //
//--------//
/**
* Convert a given string to the proper object value, as
* implemented by each subclass.
*
* @param str the encoded string
* @return the decoded object
*/
protected abstract Object decode (java.lang.String str);
//----------------//
// getCachedValue //
//----------------//
/**
* Report the current value of the constant
*
* @return the (cached) current value
*/
protected Object getCachedValue ()
{
return getTuple().cachedValue;
}
//----------//
// setTuple //
//----------//
/**
* Modify the current parameter data in an atomic way,
* and remember the very first value (the initial string).
*
* @param str The new value (as a string)
* @param val The new value (as an object)
*/
protected void setTuple (java.lang.String str,
Object val)
{
while (true) {
Tuple old = tuple.get();
Tuple temp = new Tuple(str, val);
if (old == null) {
if (tuple.compareAndSet(null, temp)) {
initialString = str;
return;
}
} else {
tuple.set(temp);
return;
}
}
}
//----------------//
// getValueOrigin //
//----------------//
/**
* Convenient method, reporting the origin of the current value for
* this constant, either SRC or USR.
*
* @return a mnemonic for the value origin
*/
java.lang.String getValueOrigin ()
{
ConstantManager mgr = ConstantManager.getInstance();
java.lang.String cur = getCurrentString();
java.lang.String usr = mgr.getConstantUserValue(qualifiedName);
java.lang.String src = sourceString;
if (cur.equals(src)) {
return "SRC";
}
if (cur.equals(usr)) {
return "USR";
}
return "???";
}
//------------------//
// checkInitialized //
//------------------//
/**
* Check the unit+name have been assigned to this constant object.
* They are mandatory to link the constant to the persistency mechanism.
*/
private void checkInitialized ()
{
int i = 0;
// Make sure everything is initialized properly
while (qualifiedName == null) {
i++;
UnitManager.getInstance().checkDirtySets();
}
// For monitoring/debugging only
if (i > 1) {
System.out.println(
"*** " + Thread.currentThread().getName()
+ " checkInitialized loop:" + i);
}
}
//----------//
// getTuple //
//----------//
/**
* Report the current tuple data, which may imply to trigger the
* assignment of qualified name to the constant, in order to get
* property data
*
* @return the current tuple data
*/
private Tuple getTuple ()
{
checkInitialized();
return tuple.get();
}
//~ Inner Classes ----------------------------------------------------------
//-------//
// Angle //
//-------//
/**
* A subclass of Double, meant to store an angle (in radians).
*/
public static class Angle
extends Constant.Double
{
//~ Constructors -------------------------------------------------------
/**
* Specific constructor, where 'unit' and 'name' are assigned later
*
* @param defaultValue the (double) default value
* @param description the semantic of the constant
*/
public Angle (double defaultValue,
java.lang.String description)
{
super("Radians", defaultValue, description);
}
}
//---------//
// Boolean //
//---------//
/**
* A subclass of Constant, meant to store a boolean value.
*/
public static class Boolean
extends Constant
{
//~ Constructors -------------------------------------------------------
/**
* Specific constructor, where 'unit' and 'name' are assigned later
*
* @param defaultValue the (boolean) default value
* @param description the semantic of the constant
*/
public Boolean (boolean defaultValue,
java.lang.String description)
{
super(null, java.lang.Boolean.toString(defaultValue), description);
}
//~ Methods ------------------------------------------------------------
/**
* Retrieve the current constant value
*
* @return the current (boolean) value
*/
public boolean getValue ()
{
return ((java.lang.Boolean) getCachedValue()).booleanValue();
}
/**
* Convenient method to access this boolean value
*
* @return true if set, false otherwise
*/
public boolean isSet ()
{
return getValue();
}
/**
* Allows to set a new boolean value (passed as a string) to this
* constant. The string validity is actually checked.
*
* @param string the boolean value as a string
*/
@Override
public void setValue (java.lang.String string)
{
setValue(java.lang.Boolean.valueOf(string).booleanValue());
}
/**
* Set a new value to the constant
*
* @param val the new (boolean) value
*/
public void setValue (boolean val)
{
setTuple(java.lang.Boolean.toString(val), val);
}
@Override
protected java.lang.Boolean decode (java.lang.String str)
{
return java.lang.Boolean.valueOf(str);
}
}
//-------//
// Color //
//-------//
/**
* A subclass of Constant, meant to store a {@link java.awt.Color}
* value.
* They have a disk repository which is separate from the other constants.
*/
public static class Color
extends Constant
{
//~ Constructors -------------------------------------------------------
/**
* Normal constructor, with a String type for default value
*
* @param unit the enclosing unit
* @param name the constant name
* @param defaultValue the default (String) RGB value
* @param description the semantic of the constant
*/
public Color (java.lang.String unit,
java.lang.String name,
java.lang.String defaultValue,
java.lang.String description)
{
super(null, defaultValue, description);
setUnitAndName(unit, name);
}
//~ Methods ------------------------------------------------------------
//-------------//
// decodeColor //
//-------------//
public static java.awt.Color decodeColor (java.lang.String str)
{
return java.awt.Color.decode(str);
}
//-------------//
// encodeColor //
//-------------//
public static java.lang.String encodeColor (java.awt.Color color)
{
return java.lang.String.format(
"#%02x%02x%02x",
color.getRed(),
color.getGreen(),
color.getBlue());
}
/**
* Retrieve the current constant value
*
* @return the current (Color) value
*/
public java.awt.Color getValue ()
{
return (java.awt.Color) getCachedValue();
}
/**
* Allows to set a new int RGB value (passed as a string) to this
* constant. The string validity is actually checked.
*
* @param string the int value as a string
*/
@Override
public void setValue (java.lang.String string)
{
setValue(java.awt.Color.decode(string));
}
/**
* Set a new value to the constant
*
* @param val the new Color value
*/
public void setValue (java.awt.Color val)
{
setTuple(encodeColor(val), val);
}
@Override
protected java.awt.Color decode (java.lang.String str)
{
return decodeColor(str);
}
}
//--------//
// Double //
//--------//
/**
* A subclass of Constant, meant to store a double value.
*/
public static class Double
extends Constant
{
//~ Constructors -------------------------------------------------------
public Double (java.lang.String quantityUnit,
double defaultValue,
java.lang.String description)
{
super(
quantityUnit,
java.lang.Double.toString(defaultValue),
description);
}
//~ Methods ------------------------------------------------------------
public double getValue ()
{
return ((DoubleValue) getCachedValue()).doubleValue();
}
public DoubleValue getWrappedValue ()
{
// Return a copy
return new DoubleValue(getValue());
}
public void setValue (double val)
{
setTuple(java.lang.Double.toString(val), new DoubleValue(val));
}
public void setValue (DoubleValue val)
{
setTuple(val.toString(), val);
}
@Override
public void setValue (java.lang.String string)
{
setValue(decode(string));
}
@Override
protected DoubleValue decode (java.lang.String str)
{
return new DoubleValue(java.lang.Double.valueOf(str));
}
}
//---------//
// Integer //
//---------//
/**
* A subclass of Constant, meant to store an int value.
*/
public static class Integer
extends Constant
{
//~ Constructors -------------------------------------------------------
/**
* Specific constructor, where 'unit' and 'name' are assigned later
*
* @param quantityUnit unit used by this value
* @param defaultValue the (int) default value
* @param description the semantic of the constant
*/
public Integer (java.lang.String quantityUnit,
int defaultValue,
java.lang.String description)
{
super(
quantityUnit,
java.lang.Integer.toString(defaultValue),
description);
}
//~ Methods ------------------------------------------------------------
/**
* Retrieve the current constant value
*
* @return the current (int) value
*/
public int getValue ()
{
return (java.lang.Integer) getCachedValue();
}
/**
* Allows to set a new int value (passed as a string) to this
* constant. The string validity is actually checked.
*
* @param string the int value as a string
*/
@Override
public void setValue (java.lang.String string)
{
setValue(java.lang.Integer.valueOf(string).intValue());
}
/**
* Set a new value to the constant
*
* @param val the new (int) value
*/
public void setValue (int val)
{
setTuple(java.lang.Integer.toString(val), val);
}
@Override
protected java.lang.Integer decode (java.lang.String str)
{
return new java.lang.Integer(str);
}
}
//-------//
// Ratio //
//-------//
/**
* A subclass of Double, meant to store a ratio or percentage.
*/
public static class Ratio
extends Constant.Double
{
//~ Constructors -------------------------------------------------------
/**
* Specific constructor, where 'unit' and 'name' are assigned later
*
* @param defaultValue the (double) default value
* @param description the semantic of the constant
*/
public Ratio (double defaultValue,
java.lang.String description)
{
super(null, defaultValue, description);
}
}
//--------//
// String //
//--------//
/**
* A subclass of Constant, meant to store a string value.
*/
public static class String
extends Constant
{
//~ Constructors -------------------------------------------------------
/**
* Normal constructor, with a string type for default value
*
* @param unit the enclosing unit
* @param name the constant name
* @param defaultValue the default (string) value
* @param description the semantic of the constant
*/
public String (java.lang.String unit,
java.lang.String name,
java.lang.String defaultValue,
java.lang.String description)
{
this(defaultValue, description);
setUnitAndName(unit, name);
}
/**
* Specific constructor, where 'unit' and 'name' are assigned later
*
* @param defaultValue the (string) default value
* @param description the semantic of the constant
*/
public String (java.lang.String defaultValue,
java.lang.String description)
{
super(null, defaultValue, description);
}
//~ Methods ------------------------------------------------------------
/**
* Retrieve the current constant value.
* Actually this is synonymous with currentString()
*
* @return the current (string) value
*/
public java.lang.String getValue ()
{
return (java.lang.String) getCachedValue();
}
/**
* Set a new string value to the constant
*
* @param val the new (string) value
*/
@Override
public void setValue (java.lang.String val)
{
setTuple(val, val);
}
@Override
protected java.lang.String decode (java.lang.String str)
{
return str;
}
}
//-------//
// Tuple //
//-------//
/**
* Class used to handle the tuple [currentString + currentValue]
* in an atomic way.
*/
private static class Tuple
{
//~ Instance fields ----------------------------------------------------
final java.lang.String currentString;
final Object cachedValue;
//~ Constructors -------------------------------------------------------
public Tuple (java.lang.String currentString,
Object cachedValue)
{
/** Current string Value */
this.currentString = currentString;
/** Current cached Value (optimized) */
this.cachedValue = cachedValue;
}
//~ Methods ------------------------------------------------------------
@Override
public java.lang.String toString ()
{
return currentString;
}
}
}