package context.arch.storage;
import context.arch.comm.DataObject;
import context.arch.comm.DataObjects;
import context.arch.discoverer.Discoverer;
import context.arch.discoverer.component.AbstractElement;
/**
* This class is a container for an attribute name, subAttributes (used for
* structures - STRUCT) and type. Implements Comparable; for proper operation
* Attributes must compare by name.
*
* @param T represents the class type of the attribute. It may be a data primitive (actually, the autoboxed equivalent wrapper class)
* such as double, int, boolean, or Objects such as String. However, to support convertion to and from string formats so that it can
* be transported through Sockets, T needs to properly implement the instance method <code>toString()</code>, and support static
* method <code>valueOf(String)</code>, which returns and instance of T.
*/
public class Attribute<T extends Comparable<? super T>> {
/** Needs to be a proper name with no white spaces or fancy characters like Java variable names. */
protected String name;
protected Class<T> type;
/**
* An attribute may have nested sub-attributes to support hierarchical relations.
* E.g.
* TODO May also be useful to represent supplementary information like min/max or possible nominal values.
*/
protected Attributes subAttributes;
/** Tag for an attribute */
public static final String ATTRIBUTE = "attribute";
/** Tag for an attribute data type */
public static final String ATTRIBUTE_TYPE = "attributeType";
/** Tag for sub-attributes; actually, not normally used */
public static final String SUB_ATTRIBUTE = Attributes.ATTRIBUTES;
public static final String METHOD_VALUE_OF = "valueOf";
public static final String METHOD_TO_STRING = "toString";
/** Tag for default attribute type */
public static final Class<String> DEFAULT_TYPE = String.class;
/**
* Constructor that takes a name, value, and type
*
* @param name of attribute to store
* @param subAttributes of this attribute
*/
public Attribute(String name, Class<T> type, Attributes subAttributes) {
this.name = name;
this.type = type;
this.subAttributes = subAttributes;
}
public Attribute(String name, Class<T> type) {
this(name, type, null);
}
/**
* Creates an instance of Attribute.
* This should be used in place of the constructor when wanting to dynamically instantiate an
* Attribute at runtime, with the type not known at design-time.
* @param <T>
* @param name
* @param type
* @return
*/
public static <T extends Comparable<? super T>> Attribute<T> instance(String name, Class<T> type) {
return new Attribute<T>(name, type);
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Attribute<T> fromDataObject(DataObject data) {
String name = data.getDataObject(ATTRIBUTE).getValue();
String typeClassName = data.getDataObject(ATTRIBUTE_TYPE).getValue();
try {
return fromDataObject(data, name, (Class<T>) Class.forName(typeClassName));
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
@SuppressWarnings("unchecked")
protected static <T extends Comparable<? super T>> Attribute<T> fromDataObject(DataObject data, String name, Class<T> type) {
try {
Attributes subAttrs = Attributes.fromDataObject(data);
return (Attribute<T>) Attribute.class
.getConstructor(String.class, Class.class, Attributes.class)
.newInstance(name, type, subAttrs);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Converts this object to a DataObject.
*
* @return Attribute object converted to an <ATTRIBUTE> DataObject
*/
public DataObject toDataObject() {
DataObjects children = new DataObjects();
children.add(new DataObject(ATTRIBUTE_TYPE, type.getName()));
if (subAttributes != null) { // has sub-attributes
children.add(subAttributes.toDataObject());
}
DataObject dobj = new DataObject(ATTRIBUTE, name, children);
return dobj;
}
// TODO: should we allow attributes to be non-primitive serializable
// objects? This helps support multivariate or complex structures
// or support that via AbstractDescriptionElements?
// we may want to support enums too; basically an attribute should be able
// to be any nominal concept
/**
* Returns the name of the attribute
*
* @return name of the stored attribute
*/
public String getName() {
return name;
}
// don't allow name to be re-set as it can mess up other Attributes maps that point to the same object, but have the wrong name keys
// /**
// * Sets the name of the attribute.
// * Use this sparingly because the name should strictly be an immutable property.
// * However, this is changed frequently by SequenceWidget.
// * @param name
// * @see SequenceWidget
// */
// public void setName(String name) {
// this.name = name;
// }
/**
* Returns the subAttributes of the stored attribute
*
* @return subAttributes of the stored attribute
*/
public Attributes getSubAttributes() {
return subAttributes;
}
public boolean hasSubAttributes() {
return subAttributes != null;
}
public void setSubAttributes(Attributes atts) {
this.subAttributes = atts;
}
/**
* Returns the datatype of the attribute
*
* @return name of the attribute
*/
public Class<T> getType() {
return type;
}
public boolean isType(Class<?> type) {
return getType().equals(type);
}
/**
* Checks whether the type is numeric: int, float, double, short, long, etc.
* @return
*/
public boolean isNumeric() {
return Number.class.isAssignableFrom(type);
}
/**
* A printable version of this class.
*
* @return String version of this class
*/
public String toString() {
return "name=" + getName() +
",type=" + getType() +
",subAtts=" + getSubAttributes();
}
/**
* Attributes can be ordered by name
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public int compareTo(Attribute<?> other) {
return this.name.compareTo(other.name);
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof Attribute<?>)) { return false; }
Attribute<?> otherAtt = (Attribute<?>) other;
if ( !this.name.equals(otherAtt.name) &&
!this.type.equals(otherAtt.type)) {
return false;
}
/*
* subAttributes
*/
if (this.subAttributes == null) {
if (otherAtt.subAttributes == null) { return true; }
else { return false; }
}
else {
return this.subAttributes.equals(otherAtt.subAttributes);
}
}
@Override
public Attribute<T> clone() {
return new Attribute<T>(name, type, subAttributes);
}
/**
* Convenience method to clone this attribute, and change its name
* @param name
* @return
*/
public Attribute<T> cloneWithNewName(String name) {
return new Attribute<T>(name, type, subAttributes);
}
/**
* Convert to value codex representation that is used by the Discoverer component model, for querying.
* Format: name+type, where '+' would be the URL encoded form of space ' '.
* @see #fromValueCodex(String)
* @see AbstractElement#fromDataObject(DataObject)
* @return
*/
public String toValueCodex() {
return name + Discoverer.FIELD_SEPARATOR + type.getName();
}
/**
* Create a Attribute (shallow with no sub-attributes) from the value codex representation
* that is used by the Discoverer component model, for querying.
* Format: name+value, where '+' would be the URL encoded form of space ' '.
* @param valueCodex
* @return
* @see #toValueCodex()
*/
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Attribute<T> fromValueCodex(String valueCodex) {
String[] args = valueCodex.split("\\+"); // "name+type" -> {name, type}
try {
Class<T> type = (Class<T>) Class.forName(args[1]);
return Attribute.instance(args[0], type);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}