resize JTable

S

steve

Thank you, Rogan...

I was starting to get worried about this, and thought I had recommended
something that was seriously flawed.

I use it a lot myself, and have never had any problems with it.

I never take any special considerations when inserting or deleting
rows, and my dates are dates in my model, so...

for inserts & deletes , you need to track them , and it is a problem if the
data is from a backend database.

for sorting.


MM9492
MM9461
MM80753
MM5596

the sorting seems to "place" compare and ignores the length. that's what i
mean by 'broken'. if you are sorting lists of part numbers, then it is not
the intended result.

steve
 
R

Roedy Green

MM9492
MM9461
MM80753
MM5596

the sorting seems to "place" compare and ignores the length. that's what i
mean by 'broken'. if you are sorting lists of part numbers, then it is not
the intended result.

The sort just follows whatever natural order Comparable you have
defined, right?

How would you like those items to sort? Perhaps I could show you how
to write a Comparable
 
S

steve

The sort just follows whatever natural order Comparable you have
defined, right?

How would you like those items to sort? Perhaps I could show you how
to write a Comparable

yes if you are volunteering ;-)

the reason my dates are broken, is more complex.
As all the data comes from the database, the dates are actually time-stamps,
which have the "microsecond part", to prevent longwinded conversions, ( i
read objects from the database not types, so that if i change my table
structure , my code does not break) for every date in every row , i just
converted to string, since for updates there is no class conversion for the
SQL for dates!!.
when i get time i will clean it up.


well normally i would prefer the letters to sort as letters & the "numbers"
as numbers"


MM123
MM9492
MM5596
MM80753
MM1234556

but we do not always use a 2 letter prefix, and some of our part numbers
(rarely) might be

12ASASA8545-878

i suspect the best way is to compare length then alpha, that should fix it.

but if possible i would like it to tie into the sun "broken" sorter.

this is how i am currently sorting.

String[] colheads2 =
{ TableColMaintainance.HIDDENCOL, TableColMaintainance.HIDDENCOL1,
"Models" ,"Flags","Born"}; // hide index cols,&define visible cols
private QueryTableModel modelqtbl = new QueryTableModel(colheads2,
query.NULLSUBQUERY); // pass in the col names needed, and an empty subquery
to get the table to display in the IDE
TableSorter sorter = new TableSorter(modelqtbl);
private JTable modelsList = new JTable(sorter); //tie in the sorter.



and the sorter is this:

package utils;

import java.awt.*;
import java.awt.event.*;

import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.*;


/**
* TableSorter is a decorator for TableModels; adding sorting
* functionality to a supplied TableModel. TableSorter does
* not store or copy the data in its TableModel; instead it maintains
* a map from the row indexes of the view to the row indexes of the
* model. As requests are made of the sorter (like getValueAt(row, col))
* they are passed to the underlying model after the row numbers
* have been translated via the internal mapping array. This way,
* the TableSorter appears to hold another copy of the table
* with the rows in a different order.
* <p/>
* TableSorter registers itself as a listener to the underlying model,
* just as the JTable itself would. Events recieved from the model
* are examined, sometimes manipulated (typically widened), and then
* passed on to the TableSorter's listeners (typically the JTable).
* If a change to the model has invalidated the order of TableSorter's
* rows, a note of this is made and the sorter will resort the
* rows the next time a value is requested.
* <p/>
* When the tableHeader property is set, either by using the
* setTableHeader() method or the two argument constructor, the
* table header may be used as a complete UI for TableSorter.
* The default renderer of the tableHeader is decorated with a renderer
* that indicates the sorting status of each column. In addition,
* a mouse listener is installed with the following behavior:
* <ul>
* <li>
* Mouse-click: Clears the sorting status of all other columns
* and advances the sorting status of that column through three
* values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to
* NOT_SORTED again).
* <li>
* SHIFT-mouse-click: Clears the sorting status of all other columns
* and cycles the sorting status of the column through the same
* three values, in the opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.
* <li>
* CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except
* that the changes to the column do not cancel the statuses of columns
* that are already sorting - giving a way to initiate a compound
* sort.
* </ul>
* <p/>
* This is a long overdue rewrite of a class of the same name that
* first appeared in the swing table demos in 1997.
*
* @author Philip Milne
* @author Brendon McLean
* @author Dan van Enckevort
* @author Parwinder Sekhon
* @version 2.0 02/27/04
*/
public class TableSorter extends AbstractTableModel {
public static final int DESCENDING = -1;
public static final int NOT_SORTED = 0;
public static final int ASCENDING = 1;
private static Directive EMPTY_DIRECTIVE = new Directive(-1,
NOT_SORTED);

public static final Comparator COMPARABLE_COMAPRATOR = new Comparator()
{
public int compare(Object o1, Object o2) {
return ((Comparable) o1).compareTo(o2);
}
};

public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
public int compare(Object o1, Object o2) {
return o1.toString().compareTo(o2.toString());
}
};
private boolean TableEmpty=false;
protected TableModel tableModel;
private Row[] viewToModel;
private int[] modelToView;
private JTableHeader tableHeader;
private MouseListener mouseListener;
private TableModelListener tableModelListener;
private Map columnComparators = new HashMap();
private List sortingColumns = new ArrayList();

public TableSorter() {
this.mouseListener = new MouseHandler();
this.tableModelListener = new TableModelHandler();
}

public TableSorter(TableModel tableModel) {
this();
setTableModel(tableModel);
}

public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
this();
setTableHeader(tableHeader);
setTableModel(tableModel);
}

private void clearSortingState() {
viewToModel = null;
modelToView = null;
}

public TableModel getTableModel() {
return tableModel;
}

public void setTableModel(TableModel tableModel) {
if (this.tableModel != null) {
this.tableModel.removeTableModelListener(tableModelListener);
}

this.tableModel = tableModel;

if (this.tableModel != null) {
this.tableModel.addTableModelListener(tableModelListener);
}

clearSortingState();
fireTableStructureChanged();
}

public JTableHeader getTableHeader() {
return tableHeader;
}

public void setTableHeader(JTableHeader tableHeader) {
if (this.tableHeader != null) {
this.tableHeader.removeMouseListener(mouseListener);

TableCellRenderer defaultRenderer =
this.tableHeader.getDefaultRenderer();

if (defaultRenderer instanceof SortableHeaderRenderer) {
this.tableHeader.setDefaultRenderer(
((SortableHeaderRenderer)
defaultRenderer).tableCellRenderer);
}
}

this.tableHeader = tableHeader;

if (this.tableHeader != null) {
this.tableHeader.addMouseListener(mouseListener);
this.tableHeader.setDefaultRenderer(
new SortableHeaderRenderer(
this.tableHeader.getDefaultRenderer()));
}
}

public boolean isSorting() {
return sortingColumns.size() != 0;
}

private Directive getDirective(int column) {
for (int i = 0; i < sortingColumns.size(); i++) {
Directive directive = (Directive) sortingColumns.get(i);

if (directive.column == column) {
return directive;
}
}

return EMPTY_DIRECTIVE;
}

public int getSortingStatus(int column) {
return getDirective(column).direction;
}

private void sortingStatusChanged() {
clearSortingState();
fireTableDataChanged();

if (tableHeader != null) {
tableHeader.repaint();
}
}

public void setSortingStatus(int column, int status) {
Directive directive = getDirective(column);

if (directive != EMPTY_DIRECTIVE) {
sortingColumns.remove(directive);
}

if (status != NOT_SORTED) {
sortingColumns.add(new Directive(column, status));
}

sortingStatusChanged();
}

protected Icon getHeaderRendererIcon(int column, int ArrowSize) {
Directive directive = getDirective(column);

if (directive == EMPTY_DIRECTIVE) {
return null;
}

return new Arrow(
directive.direction == DESCENDING, ArrowSize,
sortingColumns.indexOf(directive));
}

private void cancelSorting() {
sortingColumns.clear();
sortingStatusChanged();
}

public void setColumnComparator(Class type, Comparator comparator) {
if (comparator == null) {
columnComparators.remove(type);
} else {
columnComparators.put(type, comparator);
}
}

protected Comparator getComparator(int column) {
Class columnType = tableModel.getColumnClass(column);
Comparator comparator = (Comparator)
columnComparators.get(columnType);

if (comparator != null) {
return comparator;
}

if (Comparable.class.isAssignableFrom(columnType)) {
return COMPARABLE_COMAPRATOR;
}

return LEXICAL_COMPARATOR;
}

private Row[] getViewToModel() {
if (viewToModel == null) {
int tableModelRowCount = tableModel.getRowCount();
viewToModel = new Row[tableModelRowCount];

for (int row = 0; row < tableModelRowCount; row++) {
viewToModel[row] = new Row(row);
}
//if(tableModelRowCount==0){TableEmpty=true;}else{TableEmpty=false;}
if (isSorting()) {
Arrays.sort(viewToModel);
}
}

return viewToModel;
}

public int modelIndex(int viewIndex) {
Row[] temprow= getViewToModel(); // because we may need to initialize
it!!
//if(TableEmpty==true){return 0;}else{
// return temprow [viewIndex].modelIndex;
// }
return getViewToModel()[viewIndex].modelIndex;
}

private int[] getModelToView() {
if (modelToView == null) {
int n = getViewToModel().length;
modelToView = new int[n];

for (int i = 0; i < n; i++) {
modelToView[modelIndex(i)] = i;
}
}

return modelToView;
}

// TableModel interface methods
public int getRowCount() {
return (tableModel == null) ? 0 : tableModel.getRowCount();
}

public int getColumnCount() {
return (tableModel == null) ? 0 : tableModel.getColumnCount();
}

public String getColumnName(int column) {
return tableModel.getColumnName(column);
}

public Class getColumnClass(int column) {
return tableModel.getColumnClass(column);
}

public boolean isCellEditable(int row, int column) {
return tableModel.isCellEditable(modelIndex(row), column);
}

public Object getValueAt(int row, int column) {
return tableModel.getValueAt(modelIndex(row), column);
}

public void setValueAt(Object aValue, int row, int column) {
tableModel.setValueAt(aValue, modelIndex(row), column);
}

// Helper classes
private class Row implements Comparable {
private int modelIndex;

public Row(int index) {
this.modelIndex = index;
}

public int compareTo(Object o) {
int row1 = modelIndex;
int row2 = ((Row) o).modelIndex;

for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
Directive directive = (Directive) it.next();
int column = directive.column;
Object o1 = tableModel.getValueAt(row1, column);
Object o2 = tableModel.getValueAt(row2, column);
//added by me
// Treat empty strings like nulls
if (o1 instanceof String && (((String) o1).length() == 0)) {
o1 = null;
}

if (o2 instanceof String && (((String) o2).length() == 0)) {
o2 = null;
}
//end of added by me
int comparison = 0;

// Define null less than everything, except null.
if ((o1 == null) && (o2 == null)) {
comparison = 0;
} else if (o1 == null) {
comparison = -1;
} else if (o2 == null) {
comparison = 1;
} else
//if (o1 instanceof Comparable && o2 instanceof Comparable)
{
comparison = getComparator(column).compare(o1, o2);
}
//else{comparison =o1.toString().compareTo(o2.toString());
//}
if (comparison != 0) {
return (directive.direction == DESCENDING) ?
(-comparison)
: comparison;
}
}

return 0;
}
}
/*
// Sort nulls so they appear last, regardless
// of sort order
if ((o1 == null) && (o2 == null)) {
return 0;
} else if (o1 == null) {
return 1;
} else if (o2 == null) {
return -1;
} else if (o1 instanceof Comparable) {
if (ascending) {
return ((Comparable) o1).compareTo(o2);
} else {
return ((Comparable) o2).compareTo(o1);
}
} else {
if (ascending) {
return o1.toString().compareTo(o2.toString());
} else {
return o2.toString().compareTo(o1.toString());
}
}
*/
private class TableModelHandler implements TableModelListener {
public void tableChanged(TableModelEvent e) {
// If we're not sorting by anything, just pass the event along.

if (!isSorting()) {
clearSortingState();
fireTableChanged(e);

return;
}

//if the event is null , then eat it!!
//there seems to be some sort of bug, making the event null!!
if (e == null) {
fireTableChanged(e);

return;
}

// If the table structure has changed, cancel the sorting; the

// sorting columns may have been either moved or deleted from

// the model.
if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
cancelSorting();
fireTableChanged(e);

return;
}

// We can map a cell event through to the view without widening

// when the following conditions apply:
//
// a) all the changes are on one row (e.getFirstRow() ==
e.getLastRow()) and,
// b) all the changes are in one column (column !=
TableModelEvent.ALL_COLUMNS) and,
// c) we are not sorting on that column (getSortingStatus(column)
== NOT_SORTED) and,
// d) a reverse lookup will not trigger a sort (modelToView !=
null)
//
// Note: INSERT and DELETE events fail this test as they have
column == ALL_COLUMNS.
//
// The last check, for (modelToView != null) is to see if
modelToView
// is already allocated. If we don't do this check; sorting can
become
// a performance bottleneck for applications where cells
// change rapidly in different parts of the table. If cells
// change alternately in the sorting column and then outside of

// it this class can end up re-sorting on alternate cell updates
-
// which can be a performance problem for large tables. The last
// clause avoids this problem.
int column = e.getColumn();

if (
(modelToView != null) && (e.getFirstRow() == e.getLastRow())
&&
(column != TableModelEvent.ALL_COLUMNS) &&
(getSortingStatus(column) == NOT_SORTED)) {
int viewIndex = getModelToView()[e.getFirstRow()];
fireTableChanged(
new TableModelEvent(
TableSorter.this, viewIndex, viewIndex, column,
e.getType()));

return;
}

// Something has happened to the data that may have invalidated
the row order.
clearSortingState();
fireTableDataChanged();

return;
}
}

private class MouseHandler extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
JTableHeader h = (JTableHeader) e.getSource();
TableColumnModel columnModel = h.getColumnModel();
int viewColumn = columnModel.getColumnIndexAtX(e.getX());
int column = columnModel.getColumn(viewColumn).getModelIndex();

if (column != -1) {
int status = getSortingStatus(column);

if (!e.isControlDown() && !e.isMetaDown()) {
cancelSorting();
}

// Cycle the sorting states through {NOT_SORTED, ASCENDING,
DESCENDING} or
// {NOT_SORTED, DESCENDING, ASCENDING} depending on whether
shift is pressed.
status = status + (e.isShiftDown() ? (-1) : 1);
status = ((status + 4) % 3) - 1; // signed mod, returning
{-1, 0, 1}
setSortingStatus(column, status);
}
}
}

private static class Arrow implements Icon {
private boolean descending;
private int size;
private int priority;

public Arrow(boolean descending, int size, int priority) {
this.descending = descending;
this.size = size;
this.priority = priority;
}

public void paintIcon(Component c, Graphics g, int x, int y) {
Color color = (c == null) ? Color.BLUE : c.getBackground();

//color = Color.RED;
// In a compound sort, make each succesive triangle 20%
// smaller than the previous one.
//this is not such a good idea if there is a lot of cols to
sort!!

int dx = (int) (size / 2 * Math.pow(0.8, priority));
int dy = descending ? dx : (-dx);

// Align icon (roughly) with font baseline.
y = y + ((5 * size) / 6) + (descending ? (-dy) : 0);

int shift = descending ? 1 : (-1);
g.translate(x, y);

// Right diagonal.
g.setColor(color.darker());
g.drawLine(dx / 2, dy, 0, 0);
g.drawLine(dx / 2, dy + shift, 0, shift);

// Left diagonal.
g.setColor(color.brighter());
g.drawLine(dx / 2, dy, dx, 0);
g.drawLine(dx / 2, dy + shift, dx, shift);

// Horizontal line.
if (descending) {
g.setColor(color.darker().darker());
} else {
g.setColor(color.brighter().brighter());
}

g.drawLine(dx, 0, 0, 0);

g.setColor(color);
g.translate(-x, -y);
}

public int getIconWidth() {
return size;
}

public int getIconHeight() {
return size;
}
}

private class SortableHeaderRenderer implements TableCellRenderer {
private TableCellRenderer tableCellRenderer;

public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
this.tableCellRenderer = tableCellRenderer;
}

public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column) {
Component c = tableCellRenderer.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);

if (c instanceof JLabel) {
JLabel l = (JLabel) c;
l.setHorizontalTextPosition(JLabel.LEFT);

int modelColumn = table.convertColumnIndexToModel(column);
//this sets the size of the arrow
l.setIcon(
getHeaderRendererIcon(modelColumn,
l.getFont().getSize()+4));
}

return c;
}
}

private static class Directive {
private int column;
private int direction;

public Directive(int column, int direction) {
this.column = column;
this.direction = direction;
}
}
}
 
J

jonck

But why have two copies of the data? Why not just work on the master
copy as I did?

For me the advantage of this technique did not really become clear
until instead of a Sorter, I needed a Filter (not an "official" class,
but one I brewed up myself, it basically works like the Sorter except
it stores a subset of all the indices of the master model). In other
words, I wanted only certain rows of my table to be displayed based on
certain criteria entered by the user.
If I would have done this by actually deleting rows from my master
table, records would have been lost and every time the user wanted to
delete his previous query and enter a new one, I would have had to go
and retrieve the original data from the database/XML file/whatever.
Another advantage of this technique is that I could now layer Filters
and Sorters on top of each other, therefore giving the user the
capability of doing searches within resultsets of previous searches,
sorting in between, etc... (so one could, for example, do a search
like: give me everyone that lives in Paris, now everyone who's name
starts with a B, now sort on zipcode) and if a user wanted to
back-track a step, it was a simple case of deleting the last model in
the chain.

This type of functionality really showed me the advantage of delegating
models.

Kind regards, Jonck
 
R

Roedy Green

MM123
MM9492
MM5596
MM80753
MM1234556

but we do not always use a 2 letter prefix, and some of our part numbers
(rarely) might be

12ASASA8545-878

i suspect the best way is to compare length then alpha, that should fix it.

Here is the code. I have only tested it with the test harness. Before
you trust it, it should be given examples to exercise all the code.

The big thing wrong with it is on every key compare it goes through a
great song and dance to parse the key into fragments. Ideally that
should be done once, not once per compare.

You could have Compare cache the values or your could precompute them
on a separate pass.

However, for a sort of only a few thousand you won't notice and big
slowdown.

It is a big ugly the kludge it uses to flush the accumulations for the
last key fragment. I'd like to see a tidier way to handle that. I
wanted to avoid repeating code just to handle the last frag.


// Comparator for Catalog Numbers. Sorts alpha and numeric parts
separately.
// Sorts numeric parts numerically.

import java.lang.StringBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

class CatalogOrder implements Comparator<String>
{
/**
* Compare two Strings. Callback for sort, as a catalog number.
* effectively returns b-a;
* @return +1, b>a, 0 a=b, -1 b<a, case insenstive
*/
public final int compare( String a, String b )
{
// for speed could do tidyKey ahead of time, instead of calling
once per compare.

Object[] aa = tidyKey( a );

Object[] bb = tidyKey( b );

int min = Math.min (aa.length, bb.length );
long diff;

for ( int i=0; i<min; i++ )
{
if ( aa instanceof String && bb instanceof String )
{
diff = ((String)aa).compareTo( (String)bb );
}
else if ( aa instanceof Long && bb instanceof Long )
{
diff = ((Long)aa).longValue() -
((Long)bb).longValue();
}
else
{
// Mismatched. Not a very consistent catalog number
system.
// Treat numbers as less than letters.
return aa instanceof Long ? -1 : 1;
}
if ( diff != 0 )
{
// can't just return raw diff, since it is a long
return diff < 0 ? -1 : 1;
}
} // end for
// we fell out the bottom of the loop with detecting a
difference.
// the longer key then comes later.
if ( aa.length < bb.length ) return -1;
if ( aa.length > bb.length ) return 1;
// were absolutely identical
return 0;

} // end compare

/**
* Used create an aux sortable multifield key from a catalog
number.
* Converts an alphanumeric key to an array of mixed Strings and
Longs
* breaking catalog number into alpha and numeric fields
*/
public static Object[] tidyKey ( String catalog )
{

// chars in this catalog number
int catalogLength = catalog.length();

// 0-terminated array to hold the catalog String
// 0 lets us go round the loop one extra time to flush
accumulation.
char[] catalogChars = new char[ catalogLength + 1];

// copy catalog to char[]. Terminal 0 already in place.
for ( int i=0; i<catalogLength; i++ )
{
catalogChars = catalog.charAt( i );
}

// Where we accumulate the fragments of the the key, either
String or Long
ArrayList<Object> keys = new ArrayList<Object>( 5 );

// keep track if we are processing numbers or letters.
boolean inNumeric = false;

// where we accumulate the next chunk of key
StringBuilder accum = new StringBuilder( catalogLength );

// loop once for each char, plus one more time to flush the
append buffer.
for ( int i=0; i<catalogLength+1; i++ )
{
char c = catalogChars;
boolean isNumeric = '0'<= c && c <= '9';
if ( isNumeric != inNumeric || c == 0 )
{
// we need to finish off the previous accumulation
if ( inNumeric )
{
// finish off previous numeric field
String numeric = accum.toString();
if ( numeric.length() > 0 )
{
keys.add ( new Long( numeric ) );
}
inNumeric = false;
}
else
{
// finish off previous alpha field
String alpha = accum.toString();
if ( alpha.length() > 0 )
{
keys.add ( alpha );
}
inNumeric = true;
}
// clear for reuse
accum.setLength( 0 );
}

// tack onto key fragment we have accumulated already,
// or to newly cleared accum
accum.append ( c );
} // end for

return keys.toArray( new Object[ keys.size() ] );
} // end tidyKey

/**
* test harness
*
* @param args not used
*/
public static void main ( String[] args )
{

// test tidyKey
Object[] key = tidyKey( "12ASASA8545-878" );
for ( Object keyFrag : key )
{
System.out.println ( keyFrag.getClass().getName() + " : " +
keyFrag.toString() );
}

// try sorting some catalog numbers

String[] cats = {
"MM123",
"MM9492",
"MM80753",
"MM5596",
"MM1234556",
"12ASASA8545-878A",
"12ASASA8545-878",
"12ASASA8545-876",
};
Arrays.sort( cats, new CatalogOrder() );
for ( String cat : cats )
{
System.out.println( cat );
}
} // end main
}
 
S

steve

MM123
MM9492
MM5596
MM80753
MM1234556

but we do not always use a 2 letter prefix, and some of our part numbers
(rarely) might be

12ASASA8545-878

i suspect the best way is to compare length then alpha, that should fix it.

Here is the code. I have only tested it with the test harness. Before
you trust it, it should be given examples to exercise all the code.

The big thing wrong with it is on every key compare it goes through a
great song and dance to parse the key into fragments. Ideally that
should be done once, not once per compare.

You could have Compare cache the values or your could precompute them
on a separate pass.

However, for a sort of only a few thousand you won't notice and big
slowdown.

It is a big ugly the kludge it uses to flush the accumulations for the
last key fragment. I'd like to see a tidier way to handle that. I
wanted to avoid repeating code just to handle the last frag.


// Comparator for Catalog Numbers. Sorts alpha and numeric parts
separately.
// Sorts numeric parts numerically.

import java.lang.StringBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

class CatalogOrder implements Comparator<String>
{
/**
* Compare two Strings. Callback for sort, as a catalog number.
* effectively returns b-a;
* @return +1, b>a, 0 a=b, -1 b<a, case insenstive
*/
public final int compare( String a, String b )
{
// for speed could do tidyKey ahead of time, instead of calling
once per compare.

Object[] aa = tidyKey( a );

Object[] bb = tidyKey( b );

int min = Math.min (aa.length, bb.length );
long diff;

for ( int i=0; i<min; i++ )
{
if ( aa instanceof String && bb instanceof String )
{
diff = ((String)aa).compareTo( (String)bb );
}
else if ( aa instanceof Long && bb instanceof Long )
{
diff = ((Long)aa).longValue() -
((Long)bb).longValue();
}
else
{
// Mismatched. Not a very consistent catalog number
system.
// Treat numbers as less than letters.
return aa instanceof Long ? -1 : 1;
}
if ( diff != 0 )
{
// can't just return raw diff, since it is a long
return diff < 0 ? -1 : 1;
}
} // end for
// we fell out the bottom of the loop with detecting a
difference.
// the longer key then comes later.
if ( aa.length < bb.length ) return -1;
if ( aa.length > bb.length ) return 1;
// were absolutely identical
return 0;

} // end compare

/**
* Used create an aux sortable multifield key from a catalog
number.
* Converts an alphanumeric key to an array of mixed Strings and
Longs
* breaking catalog number into alpha and numeric fields
*/
public static Object[] tidyKey ( String catalog )
{

// chars in this catalog number
int catalogLength = catalog.length();

// 0-terminated array to hold the catalog String
// 0 lets us go round the loop one extra time to flush
accumulation.
char[] catalogChars = new char[ catalogLength + 1];

// copy catalog to char[]. Terminal 0 already in place.
for ( int i=0; i<catalogLength; i++ )
{
catalogChars = catalog.charAt( i );
}

// Where we accumulate the fragments of the the key, either
String or Long
ArrayList<Object> keys = new ArrayList<Object>( 5 );

// keep track if we are processing numbers or letters.
boolean inNumeric = false;

// where we accumulate the next chunk of key
StringBuilder accum = new StringBuilder( catalogLength );

// loop once for each char, plus one more time to flush the
append buffer.
for ( int i=0; i<catalogLength+1; i++ )
{
char c = catalogChars;
boolean isNumeric = '0'<= c && c <= '9';
if ( isNumeric != inNumeric || c == 0 )
{
// we need to finish off the previous accumulation
if ( inNumeric )
{
// finish off previous numeric field
String numeric = accum.toString();
if ( numeric.length() > 0 )
{
keys.add ( new Long( numeric ) );
}
inNumeric = false;
}
else
{
// finish off previous alpha field
String alpha = accum.toString();
if ( alpha.length() > 0 )
{
keys.add ( alpha );
}
inNumeric = true;
}
// clear for reuse
accum.setLength( 0 );
}

// tack onto key fragment we have accumulated already,
// or to newly cleared accum
accum.append ( c );
} // end for

return keys.toArray( new Object[ keys.size() ] );
} // end tidyKey

/**
* test harness
*
* @param args not used
*/
public static void main ( String[] args )
{

// test tidyKey
Object[] key = tidyKey( "12ASASA8545-878" );
for ( Object keyFrag : key )
{
System.out.println ( keyFrag.getClass().getName() + " : " +
keyFrag.toString() );
}

// try sorting some catalog numbers

String[] cats = {
"MM123",
"MM9492",
"MM80753",
"MM5596",
"MM1234556",
"12ASASA8545-878A",
"12ASASA8545-878",
"12ASASA8545-876",
};
Arrays.sort( cats, new CatalogOrder() );
for ( String cat : cats )
{
System.out.println( cat );
}
} // end main
}



thanks roedy, I'm going to take a look at this over the weekend, whilst I
tidy up my 'date' implementation in the JTable.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,276
Latest member
Sawatmakal

Latest Threads

Top