/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2003-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotools.gui.swing;
import java.util.Map;
import java.util.Date;
import java.util.HashMap;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.lang.reflect.Array;
import java.text.ParseException;
import java.text.NumberFormat;
import java.text.DateFormat;
import java.text.Format;
import javax.swing.JList; // For javadoc
import javax.swing.JTable;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.JScrollPane;
import javax.swing.JFormattedTextField;
import javax.swing.BorderFactory;
import javax.swing.table.TableModel;
import javax.swing.table.AbstractTableModel;
import java.awt.geom.AffineTransform;
import java.awt.image.DataBuffer;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.Component;
import java.awt.Dimension;
import static java.awt.GridBagConstraints.*;
import javax.media.jai.util.Range;
import javax.media.jai.KernelJAI;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.OperationNode;
import javax.media.jai.OperationDescriptor;
import javax.media.jai.PerspectiveTransform;
import javax.media.jai.ParameterListDescriptor;
import javax.media.jai.RegistryElementDescriptor;
import org.geotools.measure.Angle;
import org.geotools.measure.AngleFormat;
import org.geotools.util.logging.Logging;
import org.geotools.resources.XMath;
import org.geotools.resources.Classes;
import org.geotools.resources.Utilities;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;
import org.geotools.gui.swing.image.KernelEditor;
/**
* An editor for arbitrary parameter object. The parameter value can be any {@link Object}.
* The editor content will changes according the parameter class. For example, the content
* will be a {@link KernelEditor} if the parameter is an instance of {@link KernelJAI}.
* Currently supported parameter type includes:
*
* <ul>
* <li>Individual {@linkplain String string}, {@linkplain Number number}, {@linkplain Date date}
* or {@linkplain Angle angle}.</li>
* <li>Table of any primitive type ({@code int[]}, {@code float[]}, etc.).</li>
* <li>Matrix of any primitive type ({@code int[][]}, {@code float[][]}, etc.).</li>
* <li>JAI {@linkplain LookupTableJAI lookup table}, which are display in tabular format.</li>
* <li>{@linkplain AffineTransform Affine transform} and {@linkplain PerspectiveTransform
* perspective transform}, which are display like a matrix.</li>
* <li>Convolution {@linkplain KernelJAI kernel}, which are display in a {@link KernelEditor}.</li>
* </ul>
*
* @since 2.0
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see org.geotools.gui.swing.image.KernelEditor
* @see org.geotools.gui.swing.image.ImageProperties
* @see org.geotools.gui.swing.image.OperationTreeBrowser
*
* @todo This class do not yet support the edition of parameter value.
* We will allow that in a future version. This work is already
* partially done with the 'editable' boolean value.
*/
@SuppressWarnings("serial")
public class ParameterEditor extends JPanel {
/** Key for {@link String} node. */ private static final String STRING = "String";
/** Key for {@link Boolean} node. */ private static final String BOOLEAN = "Boolean";
/** Key for {@link Number} node. */ private static final String NUMBER = "Number";
/** Key for {@link Angle} node. */ private static final String ANGLE = "Angle";
/** Key for {@link Date} node. */ private static final String DATE = "Date";
/** Key for {@link KernelJAI} node. */ private static final String KERNEL = "Kernel";
/** Key for any kind of table node. */ private static final String TABLE = "Table";
/** Key for unrecognized types. */ private static final String DEFAULT = "Default";
/**
* The set of {@linkplain Component component} editors created up to date.
*/
private final Map<String,Component> editors = new HashMap<String,Component>();
/**
* The properties panel for parameters. The content for this panel
* depends on the selected item, but usually includes the following:
* <ul>
* <li>A {@link JTextField} for simple parameters (numbers, string, etc.)</li>
* <li>A {@link JList} for enumerated parameters.</li>
* <li>A {@link JTable} for any kind of array parameter and {@link LookupTableJAI}.</li>
* <li>A {@link KernelEditor} for {@link KernelJAI} parameters.</li>
* </ul>
*/
private final Container cards = new JPanel(new CardLayout());
/**
* The label for parameter or image description.
* Usually displayed on top of parameter editor.
*/
private final JLabel description = new JLabel(" ", JLabel.CENTER);
/**
* The current value in the process of being edited. This object is usually an instance of
* {@link Number}, {@link KernelJAI}, {@link LookupTableJAI} or some other parameter object.
*
* @see #setParameterValue
*/
private Object value;
/**
* The editor widget currently in use.
*
* @see #setParameterValue
* @see #getEditor
*/
private Component editor;
/**
* The editor model currently in use. This is often the model used by the editor widget.
*/
private Editor model;
/**
* {@code true} if this widget is editable.
*/
private static final boolean editable = false;
/**
* Constructs an initially empty parameter editor.
*/
public ParameterEditor() {
super(new BorderLayout());
description.setBorder(
BorderFactory.createCompoundBorder(description.getBorder(),
BorderFactory.createCompoundBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(6, 9, 6, 9),
BorderFactory.createLineBorder(description.getForeground())),
BorderFactory.createEmptyBorder(6, 0, 6, 0))));
add(description, BorderLayout.NORTH );
add(cards, BorderLayout.CENTER);
setPreferredSize(new Dimension(400,250));
}
/**
* Returns the parameter value currently edited, or {@code null} if none.
*/
public Object getParameterValue() {
return (model!=null) ? model.getValue() : value;
}
/**
* Set the value to edit. The editor content will be updated according the value type.
* For example if the value is an instance of {@link KernelJAI}, then the editor content
* will be changed to a {@link KernelEditor}.
*
* @param value The value to edit. This object is usually an instance of {@link Number},
* {@link KernelJAI}, {@link LookupTableJAI} or some other parameter object.
*/
public void setParameterValue(final Object value) {
final Object oldValue = this.value;
if (!Utilities.equals(value, oldValue)) {
this.value = value;
updateEditor();
firePropertyChange("value", oldValue, value);
}
}
/**
* Returns the description currently shown, or {@code null} if none.
*/
public String getDescription() {
String text = description.getText();
if (text != null) {
text = text.trim();
if (text.length() == 0) {
text = null;
}
}
return text;
}
/**
* Set the description string to write on top of the editor.
* This is usually a short description of the paramter being edited.
*/
public void setDescription(String description) {
if (description==null || description.length()==0) {
description = " ";
}
this.description.setText(description);
if (model != null) {
model.setValueRange(null,null);
}
}
/**
* Convenience method for setting the parameter description from a JAI operation node.
*
* @param operation The operation node for the current parameter.
* @param index The parameter index, or {@code -1} if unknow.
*
* @since 2.3
*/
public void setDescription(final OperationNode operation, final int index) {
String description = null;
Class type = null;
Range range = null;
if (operation != null) {
final String name, mode;
final RegistryElementDescriptor element;
final ParameterListDescriptor param;
name = operation.getOperationName();
mode = operation.getRegistryModeName();
element = operation.getRegistry().getDescriptor(mode, name);
param = element.getParameterListDescriptor(mode);
/*
* If a parameter is specified, gets the parameter type and its range of valid
* values.
*/
if (index>=0 && index<param.getNumParameters()) {
type = param.getParamClasses()[index];
range = param.getParamValueRange(param.getParamNames()[index]);
}
/*
* If the descriptor is an operation, gets the localized operation
* description or the parameter description.
*/
if (element instanceof OperationDescriptor) {
final String key;
final OperationDescriptor descriptor = (OperationDescriptor) element;
final ResourceBundle resources = descriptor.getResourceBundle(getLocale());
if (index >= 0) {
key = "arg" + index + "Desc";
} else {
key = "Description";
}
try {
description = resources.getString(key);
} catch (MissingResourceException ignore) {
// No description for this parameter. Try a global description.
try {
description = resources.getString("Description");
} catch (MissingResourceException exception) {
/*
* No description at all for this operation. Not a big deal;
* just left the description empty. Log the exception with a
* low level, since this warning is not really important. The
* level is slightly higher than in 'RegisteredOperationBrowser'
* since we have tried the global operation description as well.
*/
Logging.recoverableException(ParameterEditor.class, "setDescription", exception);
}
}
}
}
setDescription(description);
if (model != null) {
model.setValueRange(type, range);
}
}
/**
* Returns the component used for editing the parameter. The component class depends on the
* class of the value set by the last call to {@link #setParameterValue}. The editor may be
* an instance of {@link KernelEditor}, {@link JTable}, {@link JTextField}, {@link JList} or
* any other suitable component.
*
* @return The editor, or {@code null} if no value has been set.
*/
public Component getEditor() {
return editor;
}
/**
* Returns the editor for the given name. If an editor is found, it will be bring
* on top of the card layout (i.e. will become the visible editor). Otherwise, this
* method returns {@code null}.
*
* @param name The editor name. Should be one of {@link #NUMBER}, {@link #KERNEL} and
* similar constants.
* @return The editor, or {@code null}.
*/
private Component getEditor(final String name) {
final Component panel = editors.get(name);
((CardLayout) cards.getLayout()).show(cards, name);
return panel;
}
/**
* Add the specified editor. No editor must exists for the specified name prior to this
* call. The editor will be bring on top of the card layout (i.e. will become the visible
* panel).
*
* @param name The editor name. Should be one of {@link #NUMBER}, {@link #KERNEL} and
* similar constants.
* @param editor The editor.
* @param scroll {@code true} if the editor should be wrapped into a {@link JScrollPane}
* prior its addition to the container.
*/
private void addEditor(final String name, Component editor, final boolean scroll) {
if (editors.put(name, editor) != null) {
throw new IllegalStateException(name); // Should not happen.
}
if (scroll) {
editor = new JScrollPane(editor);
}
cards.add(editor, name);
((CardLayout) cards.getLayout()).show(cards, name);
}
/**
* Update the editor according the current {@link #value}. If a suitable editors already
* exists for the value class, it will be reused. Otherwise, a new editor will be created
* on the fly.
*
* The {@link #editor} field will be set to the component used for editing the parameter.
* This component may be an instance of {@link KernelEditor}, {@link JTable},
* {@link JTextField}, {@link JList} or any other suitable component.
*
* The {@link #model} field will be set to the model used by the editor widget.
*/
@SuppressWarnings("fallthrough")
private void updateEditor() {
Object value = this.value;
/*
* In the special case where the value is an array with only one element, extract
* the element and use a specialized editor as if the element wasn't in an array.
*/
while (value!=null && value.getClass().isArray() && Array.getLength(value)==1) {
value = Array.get(value, 0);
}
/*
* String --- Uses a JTextField editor.
*/
if (value instanceof String) {
Singleton editor = (Singleton) getEditor(STRING);
if (editor == null) {
editor = new Singleton(null);
addEditor(STRING, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* Boolean --- Uses a JTextField editor.
*/
if (value instanceof Boolean) {
Singleton editor = (Singleton) getEditor(BOOLEAN);
if (editor == null) {
editor = new Singleton(null); // TODO: we should define some kind of BooleanFormat.
addEditor(BOOLEAN, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* Number --- Uses a JFormattedTextField editor.
*/
if (value instanceof Number) {
Singleton editor = (Singleton) getEditor(NUMBER);
if (editor == null) {
editor = new Singleton(NumberFormat.getInstance(getLocale()));
addEditor(NUMBER, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* Date --- Uses a JFormattedTextField editor.
*/
if (value instanceof Date) {
Singleton editor = (Singleton) getEditor(DATE);
if (editor == null) {
editor = new Singleton(DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.LONG, getLocale()));
addEditor(DATE, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* Angle --- Uses a JFormattedTextField editor.
*/
if (value instanceof Angle) {
Singleton editor = (Singleton) getEditor(ANGLE);
if (editor == null) {
editor = new Singleton(new AngleFormat("D°MM.mm'", getLocale()));
addEditor(ANGLE, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* AffineTransform --- convert to a matrix for processing by the general matrix case.
*/
if (value instanceof AffineTransform) {
final AffineTransform transform = (AffineTransform) value;
value = new double[][] {
{transform.getScaleX(), transform.getShearX(), transform.getTranslateX()},
{transform.getShearY(), transform.getScaleY(), transform.getTranslateY()},
{0, 0, 1}
};
}
/*
* PerspectiveTransform --- convert to a matrix for processing by the general matrix case.
*/
if (value instanceof PerspectiveTransform) {
final double[][] matrix = new double[][] {
new double[3],
new double[3],
new double[3]
};
((PerspectiveTransform) value).getMatrix(matrix);
value = matrix;
}
/*
* Any table or matrix --- use a JTable editor.
*/
if (value != null) {
final Class elementClass = value.getClass().getComponentType();
if (elementClass != null) {
final TableModel model;
if (elementClass.isArray()) {
model = new Matrix((Object[]) value);
} else {
model = new Table(new Object[] {value}, 0, false);
}
JTable editor = (JTable) getEditor(TABLE);
if (editor == null) {
addEditor(TABLE, editor=new JTable(model), true);
} else {
editor.setModel(model);
}
this.editor = editor;
this.model = (Editor) model;
return;
}
}
/*
* LookupTableJAI --- Uses a JTable editor.
*/
if (value instanceof LookupTableJAI) {
final LookupTableJAI table = (LookupTableJAI) value;
final Object[] data;
boolean unsigned = false;
switch (table.getDataType()) {
case DataBuffer.TYPE_BYTE: data=table.getByteData(); unsigned=true; break;
case DataBuffer.TYPE_USHORT: unsigned=true; // Fall through
case DataBuffer.TYPE_SHORT: data=table.getShortData(); break;
case DataBuffer.TYPE_INT: data=table.getIntData(); break;
case DataBuffer.TYPE_FLOAT: data=table.getFloatData(); break;
case DataBuffer.TYPE_DOUBLE: data=table.getDoubleData(); break;
default: this.editor=null; this.model=null; return;
}
final Table model = new Table(data, table.getOffset(), unsigned);
JTable editor = (JTable) getEditor(TABLE);
if (editor == null) {
addEditor(TABLE, editor=new JTable(model), true);
} else {
editor.setModel(model);
}
this.editor = editor;
this.model = model;
return;
}
/*
* KernelJAI --- Uses a KernelEditor.
*/
if (value instanceof KernelJAI) {
KernelEditor editor = (KernelEditor) getEditor(KERNEL);
if (editor == null) {
editor = new KernelEditor();
editor.addDefaultKernels();
addEditor(KERNEL, editor, false);
}
editor.setKernel((KernelJAI) value);
this.editor = editor;
this.model = null; // TODO: Set the editor.
return;
}
/*
* Default case --- Uses a JTextArea
*/
JTextArea editor = (JTextArea) getEditor(DEFAULT);
if (editor == null) {
addEditor(DEFAULT, editor=new JTextArea(), true);
editor.setEditable(false);
}
editor.setText(String.valueOf(value));
this.editor = editor;
this.model = null; // TODO: Set the editor.
}
/**
* The interface for editor capable to returns the edited value.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @todo This interface should have a 'setEditable(boolean)' method.
*/
private static interface Editor {
/**
* Returns the edited value.
*/
public abstract Object getValue();
/**
* Set the type and the range of valid values.
*/
public abstract void setValueRange(final Class type, final Range range);
}
/**
* An editor panel for editing a single value. The value if usually an instance of
* {@link Number}, {@link Date}, {@link Angle}, {@link Boolean} or {@link String}.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @todo This editor should use {@code JSpinner}, but we need to gets
* the minimum and maximum values first since spinner needs bounds.
*/
private static final class Singleton extends JPanel implements Editor {
/**
* The data type.
*/
private final JLabel type = new JLabel();
/**
* The minimum allowed value.
*/
private final JLabel minimum = new JLabel();
/**
* The maximum allowed value.
*/
private final JLabel maximum = new JLabel();
/**
* The text field for editing the value.
*/
private final JTextField field;
/**
* Construct an editor for value using the specified format.
*/
public Singleton(final Format format) {
super(new GridBagLayout());
if (format != null) {
field = new JFormattedTextField(format);
} else {
field = new JTextField();
}
field.setEditable(editable);
final Vocabulary resources = Vocabulary.getResources(getLocale());
final GridBagConstraints c = new GridBagConstraints();
c.gridx=0; c.gridwidth=1; c.insets.left=9; c.fill=HORIZONTAL;
c.gridy=0; add(new JLabel(resources.getLabel(VocabularyKeys.TYPE )), c);
c.gridy++; add(new JLabel(resources.getLabel(VocabularyKeys.MINIMUM)), c);
c.gridy++; add(new JLabel(resources.getLabel(VocabularyKeys.MAXIMUM)), c);
c.gridy++; add(new JLabel(resources.getLabel(VocabularyKeys.VALUE )), c);
c.gridx=1; c.weightx=1; c.insets.right=9;
c.gridy=0; add(type, c);
c.gridy++; add(minimum, c);
c.gridy++; add(maximum, c);
c.gridy++; add(field, c);
}
/**
* Set the value to be edited.
*/
public void setValue(final Object value) {
if (field instanceof JFormattedTextField) {
((JFormattedTextField) field).setValue(value);
} else {
field.setText(String.valueOf(value));
}
}
/**
* Returns the edited value.
*/
public Object getValue() {
if (field instanceof JFormattedTextField) {
return ((JFormattedTextField) field).getValue();
} else {
return field.getText();
}
}
/**
* Set the type and the range of valid values.
*/
public void setValueRange(Class classe, final Range range) {
String type = null;
String minimum = null;
String maximum = null;
if (classe != null) {
while (classe.isArray()) {
classe = classe.getComponentType();
}
classe = XMath.primitiveToWrapper(classe);
boolean isInteger = false;
if (XMath.isReal(classe) || (isInteger=XMath.isInteger(classe))==true) {
type = Vocabulary.format(isInteger ? VocabularyKeys.SIGNED_INTEGER_$1
: VocabularyKeys.REAL_NUMBER_$1,
new Integer(XMath.getBitCount(classe)));
} else {
type = Classes.getShortName(classe);
}
}
if (range != null) {
minimum = format(range.getMinValue());
maximum = format(range.getMaxValue());
}
this.type .setText(type);
this.minimum.setText(minimum);
this.maximum.setText(maximum);
}
/**
* Format the given value.
*/
private String format(final Comparable value) {
if (value == null) {
return null;
}
if (field instanceof JFormattedTextField) try {
return ((JFormattedTextField) field).getFormatter().valueToString(value);
} catch (ParseException exception) {
// Value can't be formatted. Fall back on the 'toString()' method, which
// is okay since this string is used for informative purpose only.
}
return value.toString();
}
}
/**
* Table model for table parameters (including {@link LookupTableJAI}.
* Instance of this class are created by {@link #updateEditor} when first needed.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
private static final class Table extends AbstractTableModel implements Editor {
/**
* The table (usually an instance of {@code double[][]}).
*/
private final Object[] table;
/**
* The offset parameter (a {@link LookupTableJAI} property).
*/
private final int offset;
/**
* {@code true} if the table values are unsigned.
*/
private final boolean unsigned;
/**
* Constructs a model for the given table.
*
* @param table The table (usually an instance of {@code double[][]}).
* @param offset The offset parameter (a {@link LookupTableJAI} property).
* @param unsigned {@code true} if the table values are unsigned.
*/
public Table(final Object[] table, final int offset, final boolean unsigned) {
this.table = table;
this.offset = offset;
this.unsigned = unsigned;
}
/**
* Returns the number of rows in the table.
*/
public int getRowCount() {
int count = 0;
for (int i=0; i<table.length; i++) {
final int length = Array.getLength(table[i]);
if (length > count) {
count = length;
}
}
return count;
}
/**
* Returns the number of columns in the model.
*/
public int getColumnCount() {
return Array.getLength(table) + 1;
}
/**
* Returns the name of the column at the specified index.
*/
@Override
public String getColumnName(final int index) {
switch (index) {
case 0: return Vocabulary.format(VocabularyKeys.INDEX);
default: return Vocabulary.format(VocabularyKeys.VALUE);
}
}
/**
* Returns the most specific superclass for all the cell values.
*/
@Override
public Class getColumnClass(final int index) {
if (index==0 || unsigned) {
return Integer.class;
}
return XMath.primitiveToWrapper(table[index-1].getClass().getComponentType());
}
/**
* Tells if the specified cell is editable.
*/
@Override
public boolean isCellEditable(final int row, final int column) {
return editable && column!=0;
}
/**
* Returns the value at the specified index.
*/
public Object getValueAt(final int row, final int column) {
if (column == 0) {
return new Integer(row + offset);
}
final Object array = table[column-1];
if (unsigned) {
return new Integer(Array.getInt(array, row) & 0x7FFFFFFF);
}
return Array.get(array, row);
}
/**
* Set the value at the given index.
*/
@Override
public void setValueAt(final Object value, final int row, final int column) {
Array.set(table[column-1], row, value);
}
/**
* Returns the edited value.
*/
public Object getValue() {
return table;
}
/**
* Set the type and the range of valid values.
* The default implementation does nothing.
*/
public void setValueRange(final Class type, final Range range) {
}
}
/**
* Table model for matrix parameters. Instance of this class
* are created by {@link #updateEditor} when first needed.
*
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
private static final class Matrix extends AbstractTableModel implements Editor {
/**
* The matrix (usually an instance of {@code double[][]}).
*/
private final Object[] matrix;
/**
* Construct a model for the given matrix.
*
* @param matrix The matrix (usually an instance of {@code double[][]}).
*/
public Matrix(final Object[] matrix) {
this.matrix = matrix;
}
/**
* Returns the number of rows in the matrix.
*/
public int getRowCount() {
return matrix.length;
}
/**
* Returns the number of columns in the model. This is the length of the longest
* row in the matrix.
*/
public int getColumnCount() {
int count = 0;
for (int i=0; i<matrix.length; i++) {
final int length = Array.getLength(matrix[i]);
if (length > count) {
count = length;
}
}
return count;
}
/**
* Returns the name of the column at the specified index.
*/
@Override
public String getColumnName(final int index) {
return Integer.toString(index);
}
/**
* Returns the most specific superclass for all the cell values.
*/
@Override
public Class getColumnClass(final int index) {
return XMath.primitiveToWrapper(matrix.getClass().getComponentType().getComponentType());
}
/**
* Tells if the specified cell is editable.
*/
@Override
public boolean isCellEditable(final int row, final int column) {
return editable;
}
/**
* Returns the value at the specified index.
*/
public Object getValueAt(final int row, final int column) {
final Object array = matrix[row];
return (column < Array.getLength(array)) ? Array.get(array, column) : null;
}
/**
* Set the value at the given index.
*/
@Override
public void setValueAt(final Object value, final int row, final int column) {
Array.set(matrix[row], column, value);
}
/**
* Returns the edited value.
*/
public Object getValue() {
return matrix;
}
/**
* Set the type and the range of valid values.
* The default implementation does nothing.
*/
public void setValueRange(final Class type, final Range range) {
}
}
}