org.rhq.enterprise.communications.command.param.ParameterDefinition Maven / Gradle / Ivy
/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* 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 version 2 of the License.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.communications.command.param;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import mazz.i18n.Logger;
import org.rhq.enterprise.communications.command.Command;
import org.rhq.enterprise.communications.i18n.CommI18NFactory;
import org.rhq.enterprise.communications.i18n.CommI18NResourceKeys;
/**
* Defines a parameter that is accepted by a {@link Command}. This class also provides some convienence methods to check
* if a value conforms to this parameter and to convert a value so it does conform.
*
* Note that a parameter definition is equal to another parameter definition if they have the same name. Equality is
* based solely on name, irregardless of any other metadata defined in this object.
*
* @author John Mazzitelli
*/
public class ParameterDefinition implements Serializable {
/**
* Logger
*/
private static final Logger LOG = CommI18NFactory.getLogger(ParameterDefinition.class);
/**
* Indicator that says a parameter is required
*/
public static final boolean REQUIRED = true;
/**
* Indicator that says a parameter is optional
*/
public static final boolean OPTIONAL = false;
/**
* Indicator that says a parameter is nullable (the parameter's value may be null
)
*/
public static final boolean NULLABLE = true;
/**
* Indicator that says a parameter is not nullable (the parameter's value must not be null
)
*/
public static final boolean NOT_NULLABLE = false;
/**
* Indicator that says a parameter should be hidden from a user's view. Used by user interfaces.
*/
public static final boolean HIDDEN = true;
/**
* Indicator that says a parameter should be visible to a user. Used by user interfaces.
*/
public static final boolean NOT_HIDDEN = false;
/**
* The name of the parameter - this is the name you use to look up the parameter value via
* {@link Command#getParameterValue(String)}.
*/
private final String m_name;
/**
* The type of the parameter specified "the Java way" (e.g. java.lang.String).
*/
private final String m_type;
/**
* if true
, this parameter's value is required to execute a command successfully.
*/
private final boolean m_required;
/**
* if true
, this parameter's value is allowed to be null
.
*/
private final boolean m_nullable;
/**
* if true
, this parameter's existence should be hidden from view from a user interface. A user should
* not know about this parameter if it is hidden.
*/
private final boolean m_hidden;
/**
* a description of the parameter's function
*/
private final String m_description;
/**
* information useful to clients who are trying to render the parameter for reading/editing
*/
private ParameterRenderingInformation m_renderingInfo;
/**
* the UID to identify the serializable version of this class
*/
private static final long serialVersionUID = 1L;
/**
* Constructor for {@link ParameterDefinition} that defines a parameter whose value is optional and may be nullable.
* The rendering information is used for other metadata (such as if the parameter is hidden and its description).
*
* @param name the name of the parameter (this is the name you use to look up the parameter value via
* {@link Command#getParameterValue(String)} (must not be null
)
* @param type the parameter data type specified "the Java way"; e.g. "java.lang.String" (must not be
* null
)
* @param renderingInfo information relating to how the parameter should be rendered by clients. See
* {@link ParameterRenderingInformation}
*
* @throws IllegalArgumentException if name
or type
is null
*/
public ParameterDefinition(String name, String type, ParameterRenderingInformation renderingInfo)
throws IllegalArgumentException {
this(name, type, false, null, renderingInfo);
}
/**
* Constructor for {@link ParameterDefinition} that defines a parameter whose value is optional and may be nullable.
*
* @param name the name of the parameter (this is the name you use to look up the parameter value via
* {@link Command#getParameterValue(String)} (must not be null
)
* @param type the parameter data type specified "the Java way"; e.g. "java.lang.String" (must not be
* null
)
* @param hidden if true
, this parameter should be hidden from any user interface - the user
* should not know about this parameter (although true
values do not prohibit users
* from setting this parameter's value should they choose to)
* @param description a human readable description string that describes the function of the parameter (may be
* null
)
*
* @throws IllegalArgumentException if name
or type
is null
*/
public ParameterDefinition(String name, String type, boolean hidden, String description)
throws IllegalArgumentException {
this(name, type, hidden, description, (ParameterRenderingInformation) null);
}
/**
* Constructor for {@link ParameterDefinition} that defines a parameter whose value is optional and may be nullable.
* Note that hidden
and description
are default values and can be overridden by the
* analogous values found in the {@link #getRenderingInfo()}.
*
* @param name the name of the parameter (this is the name you use to look up the parameter value via
* {@link Command#getParameterValue(String)} (must not be null
)
* @param type the parameter data type specified "the Java way"; e.g. "java.lang.String" (must not be
* null
)
* @param hidden if true
, this parameter should be hidden from any user interface - the user
* should not know about this parameter (although true
values do not prohibit
* users from setting this parameter's value should they choose to)
* @param description a human readable description string that describes the function of the parameter (may be
* null
)
* @param renderingInfo information relating to how the parameter should be rendered by clients. See
* {@link ParameterRenderingInformation}
*
* @throws IllegalArgumentException if name
or type
is null
*/
public ParameterDefinition(String name, String type, boolean hidden, String description,
ParameterRenderingInformation renderingInfo) throws IllegalArgumentException {
this(name, type, false, true, hidden, description, renderingInfo);
}
/**
* Constructor for {@link ParameterDefinition} that allows for a description string.
*
* @param name the name of the parameter (this is the name you use to look up the parameter value via
* {@link Command#getParameterValue(String)} (must not be null
)
* @param type the parameter data type specified "the Java way"; e.g. "java.lang.String" (must not be
* null
)
* @param required if true
, the parameter's value is required to successfully execute a command
* @param nullable if true
, the parameter's value is allowed to be null
* @param hidden if true
, this parameter should be hidden from any user interface - the user
* should not know about this parameter (although true
values do not prohibit users
* from setting this parameter's value should they choose to)
* @param description a human readable description string that describes the function of the parameter (may be
* null
)
*
* @throws IllegalArgumentException if name
or type
is null
*/
public ParameterDefinition(String name, String type, boolean required, boolean nullable, boolean hidden,
String description) throws IllegalArgumentException {
this(name, type, required, nullable, hidden, description, (ParameterRenderingInformation) null);
}
/**
* Constructor for {@link ParameterDefinition} that allows for a description string. Note that hidden
* and description
are default values and can be overridden by the analogous values found in the
* {@link #getRenderingInfo()}
*
* @param name the name of the parameter (this is the name you use to look up the parameter value via
* {@link Command#getParameterValue(String)} (must not be null
)
* @param type the parameter data type specified "the Java way"; e.g. "java.lang.String" (must not be
* null
)
* @param required if true
, the parameter's value is required to successfully execute a command
* @param nullable if true
, the parameter's value is allowed to be null
* @param hidden if true
, this parameter should be hidden from any user interface - the user
* should not know about this parameter (although true
values do not prohibit
* users from setting this parameter's value should they choose to)
* @param description a human readable description string that describes the function of the parameter (may be
* null
)
* @param renderingInfo information relating to how the parameter should be rendered by clients. See
* {@link ParameterRenderingInformation}
*
* @throws IllegalArgumentException if name
or type
is null
*/
public ParameterDefinition(String name, String type, boolean required, boolean nullable, boolean hidden,
String description, ParameterRenderingInformation renderingInfo) throws IllegalArgumentException {
if (name == null) {
throw new IllegalArgumentException("name=null");
}
if (type == null) {
throw new IllegalArgumentException("type=null");
}
m_name = name;
m_type = type;
m_required = required;
m_nullable = nullable;
m_hidden = hidden;
m_description = description;
// this way clients will always have something to render with
// even if its just the regular name and description,
// subclasses can override getDefaultRenderingInfo to provide their
// own settings, FixedValuesParameterDefinition does this
if (renderingInfo == null) {
renderingInfo = getDefaultRenderingInfo();
}
m_renderingInfo = renderingInfo;
}
/**
* Returns the value of the name of the parameter.
*
* @return parameter name
*/
public String getName() {
return m_name;
}
/**
* Returns the type of the parameter's value. The returned type string follows the Java conventions on type
* specification strings (i.e. java.lang.Integer
). See java.lang.Class
javadocs for more
* information.
*
* @return the type of the paramter's value
*/
public String getType() {
return m_type;
}
/**
* Defines whether or not the parameter value is required to exist in order to be able to successfully execute a
* command.
*
* @return if true
, this parameter must be specified when invoking a command
*/
public boolean isRequired() {
return m_required;
}
/**
* Defines whether or not the parameter's value is allowed to be null
.
*
* @return if true
, the parameter's value is allowed to be null
*/
public boolean isNullable() {
return m_nullable;
}
/**
* Returns a flag to indicate if this parameter should be hidden from user interfaces. If true
, this
* parameter should not be known to the user and thus the user interfaces should not show this parameter's
* existence. If false
, this parameter should be visible to users and hence can be shown to users via a
* user interface (which is typically the case).
*
* @return flag to indicate if this parameter should be hidden from a user or if it should be visible to the user
*/
public boolean isHidden() {
boolean flag = m_hidden;
// rendering information overrides the default hidden flag, but only if rendering info is available
if (getRenderingInfo() != null) {
flag = getRenderingInfo().isHidden();
}
return flag;
}
/**
* Returns a description indicating the purpose and function of the parameter. This may be null
if it
* was never defined.
*
* @return human readable description string
*/
public String getDescription() {
String desc = m_description;
// rendering information overrides the default description, but only if rendering info is available
if (getRenderingInfo() != null) {
desc = getRenderingInfo().getDescription();
}
return desc;
}
/**
* Get the rendering information that can be used to render the parameter in a user interface.
*
* @return the rendering info
*/
public ParameterRenderingInformation getRenderingInfo() {
return m_renderingInfo;
}
/**
* Sets the rendering information that can be used to render the parameter in a user interface.
*
* @param renderingInfo the new rendering info
*/
public void setRenderingInfo(ParameterRenderingInformation renderingInfo) {
m_renderingInfo = renderingInfo;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuffer strbuf = new StringBuffer("ParamDef: ");
strbuf.append("name=[");
strbuf.append(m_name);
strbuf.append("]; type=[");
strbuf.append(m_type);
strbuf.append("]; required=[");
strbuf.append(m_required);
strbuf.append("]; nullable=[");
strbuf.append(m_nullable);
strbuf.append("]; hidden=[");
strbuf.append(m_hidden);
strbuf.append("]; description=[");
strbuf.append(m_description);
strbuf.append("]");
return strbuf.toString();
}
/**
* Equality is based solely on {@link #getName() name} - a definition is the same as another if their names are the
* same.
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if ((obj == null) || (!(obj instanceof ParameterDefinition))) {
return false;
}
return this.m_name.equals(((ParameterDefinition) obj).m_name);
}
/**
* The hash code for a parameter definition is the same hash code as the parameter definition
* {@link #getName() name}.
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return m_name.hashCode();
}
/**
* Checks the validity of the given object to ensure it conforms to this parameter definition. The given object's
* type is compared to the parameter's desired {@link #getType() type}. If the given object is an instance of the
* parameter's type, this method returns true
. The actual type check is performed via
* java.lang.Class.isInstance(Object)
.
*
*
Nullability is also checked (that is, if the given object is null
, this parameter definition must
* {@link #isNullable() allow for null}.
*
* Note that if the parameter type class (i.e. java.lang.Class.forName({@link #getType()}) is unknown or
* unloadable, this method returns false
.
*
* @param valueToCheck checking the type validity of this object
*
* @return true
if the given object conforms to this parameter definition, false
otherwise
*/
public boolean isValidValue(Object valueToCheck) {
boolean valid;
if (valueToCheck == null) {
valid = isNullable();
} else {
try {
Class parameterTypeClass = Class.forName(getType());
valid = parameterTypeClass.isInstance(valueToCheck);
} catch (ClassNotFoundException e) {
valid = false;
}
}
return valid;
}
/**
* This method converts the given objectToConvert
value into an instance of this parameter's
* {@link #getType() type}. If the object to convert is not already of the parameter's type, a constructor is called
* to build one. It will be assumed that this parameter's type class has a constructor that takes a single argument
* of the same type as objectToConvert
. If this constructor does exist, it is used to create the new
* instance by passing objectToConvert
to it. The resulting object is returned (this object will be of
* the parameter's defined type. The original objectToConvert
will be returned as-is (i.e. no
* conversion will be performed) if either of the following is true:
*
*
* objectToConvert
is null
* objectToConvert
is already an instance of this parameter's type
*
*
* Note that if objectToConvert
is null
, but this parameter definition
* {@link #isNullable() does not allow for null}, an exception is thrown.
*
* This method is useful when needing to convert text-based command line parameters to their actual Java type
* representations.
*
* @param objectToConvert the object to convert to the given type
*
* @return the converted object
*
* @throws InvalidParameterValueException if the given object is null
but this parameter definition
* does not allow for null
, or the parameter's type specifies a
* primitive type, or the conversion failed due to a problem occurring while
* instantiating the new typed object
*/
public Object convertObject(Object objectToConvert) throws InvalidParameterValueException {
if (objectToConvert == null) {
if (!isNullable()) {
throw new InvalidParameterValueException(LOG.getMsgString(
CommI18NResourceKeys.PARAMETER_DEFINITION_NOT_NULLABLE, m_name));
}
return objectToConvert;
}
String conversionClassString = getType();
Class conversionClass;
try {
conversionClass = Class.forName(conversionClassString);
} catch (ClassNotFoundException cnfe) {
throw new InvalidParameterValueException(cnfe);
}
Object convertedObject;
if (!conversionClass.isInstance(objectToConvert)) {
convertedObject = convertObject(objectToConvert, conversionClass);
// double-check that our new converted value is really valid now
if (!isValidValue(convertedObject)) {
throw new InvalidParameterValueException(LOG.getMsgString(
CommI18NResourceKeys.PARAMETER_DEFINITION_STILL_NOT_VALID, convertedObject.getClass()));
}
} else {
// no conversion necessary
convertedObject = objectToConvert;
}
return convertedObject;
}
/**
* Returns this definition's default rendering information. If no rendering information is provided to this object's
* constructors, then the returned rendering info from this method is used. Subclasses are free to override this
* method to define their own default rendering information.
*
* This method implementation will by default render all
*
* @return a default rendering information object that can be used by this parameter definition object
*/
protected ParameterRenderingInformation getDefaultRenderingInfo() {
ParameterRenderingInformation renderingInfo;
Class valueClass = getClassFromTypeName(m_type);
if (Map.class.isAssignableFrom(valueClass)) {
renderingInfo = new TextFieldRenderingInformation(50, 5);
} else if (Collection.class.isAssignableFrom(valueClass) || valueClass.isArray()) {
renderingInfo = new TextFieldRenderingInformation(50, 1);
} else {
renderingInfo = new ParameterRenderingInformation();
}
setDefaultRenderingAttributes(renderingInfo);
return renderingInfo;
}
/**
* Sets the label, description and isHidden fields on the passed in renderingInfo
object using this
* definition for the default values. The purpose of this method is to allow this class (or subclasses) to populate
* a given rendering info object with a set of appropriate default values. Subclasses are free to use this method or
* override it to fill in rendering info with their own set of defaults.
*
* @param renderingInfo the rendering information that is to be populated with default attribute values
*/
protected void setDefaultRenderingAttributes(ParameterRenderingInformation renderingInfo) {
renderingInfo.setLabel(m_name);
renderingInfo.setDescription(m_description);
renderingInfo.setHidden(m_hidden);
return;
}
/**
* Convienence method that returns a Class
object for the given type name. If the class is
* null
, an empty string or an invalid class name, a runtime exception will be thrown.
*
* @param className the type name as a string
*
* @return the Class
representation of the given type name string
*
* @throws IllegalArgumentException if the className
is null
, empty or invalid
*/
private static Class getClassFromTypeName(String className) throws IllegalArgumentException {
if ((className == null) || className.equals("")) {
throw new IllegalArgumentException("className=null");
}
try {
Class clazz = Class.forName(className);
return clazz;
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(LOG.getMsgString(CommI18NResourceKeys.CLASS_NOT_FOUND, className));
}
}
/**
* This method converts the given objectToConvert
value into an instance of the given type. See
* {@link #convertObject(Object)} for additional information on how the convertion works.
*
* @param objectToConvert the object to convert to the given type
* @param conversionClass the type to convert to
*
* @return the converted object that is of type conversionClass
*
* @throws InvalidParameterValueException if the specified conversionClass
is a primitive type, or the
* conversion failed due to a problem occurring while instantiating the new
* typed object
*/
private Object convertObject(Object objectToConvert, Class conversionClass) throws InvalidParameterValueException {
if (conversionClass.isPrimitive()) {
throw new InvalidParameterValueException(LOG.getMsgString(CommI18NResourceKeys.CANNOT_CONVERT_PRIMITIVE,
conversionClass));
}
if (conversionClass.isArray()) {
// special processing is required to convert to an array type
return convertArrayObject(objectToConvert, conversionClass);
}
// IF the object to convert is not already of the desired type
// THEN
// Get all the constructors supported by the desired type
// FOR each constructor AND we haven't already converted the value yet
// IF the current constructor has a single parameter whose type the original object can be cast to
// THEN
// Call that constructor, passing in the original object, thus converting the original to the new type
// END IF
// END FOR
// ELSE
// Pass back the object as-is since it is already of the desired type
// END IF
Object convertedObject = null;
if (!conversionClass.isInstance(objectToConvert)) {
// if an exception occurred while attempting to call a constructor whose signature looks like one
// that should work for us, that exception will be recorded here
Exception constructorInvocationException = null;
Constructor[] constructors = conversionClass.getConstructors();
for (int i = 0; (i < constructors.length) && (convertedObject == null); i++) {
Class[] params = constructors[i].getParameterTypes();
if ((params.length == 1) && params[0].isInstance(objectToConvert)) {
try {
convertedObject = constructors[i].newInstance(new Object[] { objectToConvert });
} catch (Exception e) {
// remember this exception, but do not abort -- keep checking, maybe we'll get lucky and another
// overloaded constructor with a compatible parameter is available to us
constructorInvocationException = e;
}
}
}
// we could not convert the object to the desired type
if (convertedObject == null) {
if (constructorInvocationException != null) {
throw new InvalidParameterValueException(LOG.getMsgString(
CommI18NResourceKeys.PARAMETER_DEFINITION_CANNOT_CONVERT, conversionClass),
constructorInvocationException);
}
throw new InvalidParameterValueException(LOG.getMsgString(
CommI18NResourceKeys.PARAMETER_DEFINITION_CANNOT_CONVERT_NO_CONSTRUCTOR, conversionClass,
objectToConvert.getClass()));
}
} else {
convertedObject = objectToConvert;
}
return convertedObject;
}
/**
* Converts the given object where that object actually represents an array of objects. If the
* objectToConvert
is an actual array itself, each object in that array will be converted to this
* definition's type. Otherwise, the objectToConvert
's toString() will be tokenized and each token will
* be converted to this definition's type. If the object is a tokenizable string, it can denote its delimiter if its
* first character is one of the following:
*
*
* - ,
* - .
* - ;
* - :
* - |
* - /
* - !
* - #
* - $
* - %
* - ^
* - &
* - *
* - -
* - _
* - +
* - =
* - space
* - tab
* - newline
* - carriage-return
*
*
* If the first character is not one of the above, the default delimiter is a comma (,). If the string is
* null
or has 0*length, the returned converted array will be empty.
*
* The objectToConvert
must not be null
.
*
* @param objectToConvert the object to convert (must be either an array or have tokenizable
* toString()
)
* @param conversionClass the array class to convert to
*
* @return the returned object array whose elements are of the converted type
*
* @throws InvalidParameterValueException if the conversion failed due to a problem occurring while instantiating
* the new typed object array
*/
private Object[] convertArrayObject(Object objectToConvert, Class conversionClass)
throws InvalidParameterValueException {
Object[] objectArrayToConvert;
// If the object to convert is not an array, then assume its String form is tokenizable and convert each string token
if (!objectToConvert.getClass().isArray()) {
List stringArray = new ArrayList();
// convert the object to a tokenizable string
String objectToString = objectToConvert.toString();
if (objectToString == null) {
objectToString = "";
}
if (objectToString.length() > 0) {
// determine what the delimiter should be
String delimiters = ",.;:|/!#$%^&*-_+= \t\n\r";
char firstChar = objectToString.charAt(0);
char theDelimiter = (delimiters.indexOf(firstChar) != -1) ? firstChar : ',';
// go through the array elements in the string and put them in the objectArrayToConvert
StringTokenizer strtok = new StringTokenizer(objectToString, Character.toString(theDelimiter));
while (strtok.hasMoreTokens()) {
stringArray.add((String) strtok.nextElement());
}
}
objectArrayToConvert = stringArray.toArray(new String[stringArray.size()]);
} else {
objectArrayToConvert = (Object[]) objectToConvert;
}
// determine the type of the array elements (which may in turn be arrays themselves; enter the realm of recursion to convert multi-dim arrays)
Class arrayElementType = conversionClass.getComponentType();
// prepare our return array of converted objects - we know the size of it by the size of the array of the objects to convert
Object[] retConvertedArray = (Object[]) Array.newInstance(arrayElementType, objectArrayToConvert.length);
// go through each object to convert, and convert it the normal way via convertObject
for (int i = 0; i < objectArrayToConvert.length; i++) {
retConvertedArray[i] = convertObject(objectArrayToConvert[i], arrayElementType);
}
return retConvertedArray;
}
}