UnsatisfiedLinkError: How to create an unsynchronized deflater? - java

I would like to create a version of Java's deflater not synchronizing on its ZStreamRef instance.
I started by copying the code into a Deflater2 class. The code compiles, however, when I create an instance of that class, I get:
Exception in thread "main" java.lang.UnsatisfiedLinkError: net.dwst.bricolo.ResetGZip.Deflater2.initIDs()V
at net.mypackage.Deflater2.initIDs(Native Method)
at net.mypackage.Deflater2.<clinit>(Deflater2.java:65)
at net.mypackage.SpeedTest.main(SpeedTest.java:13)
How can I create an instance of Deflater2 without triggering this exception?
I am providing the code for Deflater2:
package net.mypackage;
public class Deflater2 {
private final ZStreamRef zsRef;
private byte[] buf = new byte[0];
private int off, len;
private int level, strategy;
private boolean setParams;
private boolean finish, finished;
/**
* Compression method for the deflate algorithm (the only one currently
* supported).
*/
public static final int DEFLATED = 8;
/**
* Compression level for no compression.
*/
public static final int NO_COMPRESSION = 0;
/**
* Compression level for fastest compression.
*/
public static final int BEST_SPEED = 1;
/**
* Compression level for best compression.
*/
public static final int BEST_COMPRESSION = 9;
/**
* Default compression level.
*/
public static final int DEFAULT_COMPRESSION = -1;
/**
* Compression strategy best used for data consisting mostly of small
* values with a somewhat random distribution. Forces more Huffman coding
* and less string matching.
*/
public static final int FILTERED = 1;
/**
* Compression strategy for Huffman coding only.
*/
public static final int HUFFMAN_ONLY = 2;
/**
* Default compression strategy.
*/
public static final int DEFAULT_STRATEGY = 0;
static {
/* Zip library is loaded from System.initializeSystemClass */
initIDs();
}
/**
* Creates a new compressor using the specified compression level.
* If 'nowrap' is true then the ZLIB header and checksum fields will
* not be used in order to support the compression format used in
* both GZIP and PKZIP.
* #param level the compression level (0-9)
* #param nowrap if true then use GZIP compatible compression
*/
public Deflater2(int level, boolean nowrap) {
this.level = level;
this.strategy = DEFAULT_STRATEGY;
this.zsRef = new ZStreamRef(init(level, DEFAULT_STRATEGY, nowrap));
}
/**
* Creates a new compressor using the specified compression level.
* Compressed data will be generated in ZLIB format.
* #param level the compression level (0-9)
*/
public Deflater2(int level) {
this(level, false);
}
/**
* Creates a new compressor with the default compression level.
* Compressed data will be generated in ZLIB format.
*/
public Deflater2() {
this(DEFAULT_COMPRESSION, false);
}
/**
* Sets input data for compression. This should be called whenever
* needsInput() returns true indicating that more input data is required.
* #param b the input data bytes
* #param off the start offset of the data
* #param len the length of the data
* #see Deflater#needsInput
*/
public void setInput(byte[] b, int off, int len) {
if (b== null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || off > b.length - len) {
throw new ArrayIndexOutOfBoundsException();
}
synchronized (zsRef) {
this.buf = b;
this.off = off;
this.len = len;
}
}
/**
* Sets input data for compression. This should be called whenever
* needsInput() returns true indicating that more input data is required.
* #param b the input data bytes
* #see Deflater#needsInput
*/
public void setInput(byte[] b) {
setInput(b, 0, b.length);
}
/**
* Sets preset dictionary for compression. A preset dictionary is used
* when the history buffer can be predetermined. When the data is later
* uncompressed with Inflater.inflate(), Inflater.getAdler() can be called
* in order to get the Adler-32 value of the dictionary required for
* decompression.
* #param b the dictionary data bytes
* #param off the start offset of the data
* #param len the length of the data
* #see Inflater#inflate
* #see Inflater#getAdler
*/
public void setDictionary(byte[] b, int off, int len) {
if (b == null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || off > b.length - len) {
throw new ArrayIndexOutOfBoundsException();
}
synchronized (zsRef) {
ensureOpen();
setDictionary(zsRef.address(), b, off, len);
}
}
/**
* Sets preset dictionary for compression. A preset dictionary is used
* when the history buffer can be predetermined. When the data is later
* uncompressed with Inflater.inflate(), Inflater.getAdler() can be called
* in order to get the Adler-32 value of the dictionary required for
* decompression.
* #param b the dictionary data bytes
* #see Inflater#inflate
* #see Inflater#getAdler
*/
public void setDictionary(byte[] b) {
setDictionary(b, 0, b.length);
}
/**
* Sets the compression strategy to the specified value.
* #param strategy the new compression strategy
* #exception IllegalArgumentException if the compression strategy is
* invalid
*/
public void setStrategy(int strategy) {
switch (strategy) {
case DEFAULT_STRATEGY:
case FILTERED:
case HUFFMAN_ONLY:
break;
default:
throw new IllegalArgumentException();
}
synchronized (zsRef) {
if (this.strategy != strategy) {
this.strategy = strategy;
setParams = true;
}
}
}
/**
* Sets the current compression level to the specified value.
* #param level the new compression level (0-9)
* #exception IllegalArgumentException if the compression level is invalid
*/
public void setLevel(int level) {
if ((level < 0 || level > 9) && level != DEFAULT_COMPRESSION) {
throw new IllegalArgumentException("invalid compression level");
}
synchronized (zsRef) {
if (this.level != level) {
this.level = level;
setParams = true;
}
}
}
/**
* Returns true if the input data buffer is empty and setInput()
* should be called in order to provide more input.
* #return true if the input data buffer is empty and setInput()
* should be called in order to provide more input
*/
public boolean needsInput() {
return len <= 0;
}
/**
* When called, indicates that compression should end with the current
* contents of the input buffer.
*/
public void finish() {
synchronized (zsRef) {
finish = true;
}
}
/**
* Returns true if the end of the compressed data output stream has
* been reached.
* #return true if the end of the compressed data output stream has
* been reached
*/
public boolean finished() {
synchronized (zsRef) {
return finished;
}
}
/**
* Fills specified buffer with compressed data. Returns actual number
* of bytes of compressed data. A return value of 0 indicates that
* needsInput() should be called in order to determine if more input
* data is required.
* #param b the buffer for the compressed data
* #param off the start offset of the data
* #param len the maximum number of bytes of compressed data
* #return the actual number of bytes of compressed data
*/
public int deflate(byte[] b, int off, int len) {
if (b == null) {
throw new NullPointerException();
}
if (off < 0 || len < 0 || off > b.length - len) {
throw new ArrayIndexOutOfBoundsException();
}
synchronized (zsRef) {
ensureOpen();
return deflateBytes(zsRef.address(), b, off, len);
}
}
/**
* Fills specified buffer with compressed data. Returns actual number
* of bytes of compressed data. A return value of 0 indicates that
* needsInput() should be called in order to determine if more input
* data is required.
* #param b the buffer for the compressed data
* #return the actual number of bytes of compressed data
*/
public int deflate(byte[] b) {
return deflate(b, 0, b.length);
}
/**
* Returns the ADLER-32 value of the uncompressed data.
* #return the ADLER-32 value of the uncompressed data
*/
public int getAdler() {
synchronized (zsRef) {
ensureOpen();
return getAdler(zsRef.address());
}
}
/**
* Returns the total number of uncompressed bytes input so far.
*
* <p>Since the number of bytes may be greater than
* Integer.MAX_VALUE, the {#link #getBytesRead()} method is now
* the preferred means of obtaining this information.</p>
*
* #return the total number of uncompressed bytes input so far
*/
public int getTotalIn() {
return (int) getBytesRead();
}
/**
* Returns the total number of uncompressed bytes input so far.</p>
*
* #return the total (non-negative) number of uncompressed bytes input so far
* #since 1.5
*/
public long getBytesRead() {
synchronized (zsRef) {
ensureOpen();
return getBytesRead(zsRef.address());
}
}
/**
* Returns the total number of compressed bytes output so far.
*
* <p>Since the number of bytes may be greater than
* Integer.MAX_VALUE, the {#link #getBytesWritten()} method is now
* the preferred means of obtaining this information.</p>
*
* #return the total number of compressed bytes output so far
*/
public int getTotalOut() {
return (int) getBytesWritten();
}
/**
* Returns the total number of compressed bytes output so far.</p>
*
* #return the total (non-negative) number of compressed bytes output so far
* #since 1.5
*/
public long getBytesWritten() {
synchronized (zsRef) {
ensureOpen();
return getBytesWritten(zsRef.address());
}
}
/**
* Resets deflater so that a new set of input data can be processed.
* Keeps current compression level and strategy settings.
*/
public void reset() {
synchronized (zsRef) {
ensureOpen();
reset(zsRef.address());
finish = false;
finished = false;
off = len = 0;
}
}
/**
* Closes the compressor and discards any unprocessed input.
* This method should be called when the compressor is no longer
* being used, but will also be called automatically by the
* finalize() method. Once this method is called, the behavior
* of the Deflater object is undefined.
*/
public void end() {
synchronized (zsRef) {
long addr = zsRef.address();
zsRef.clear();
if (addr != 0) {
end(addr);
buf = null;
}
}
}
/**
* Closes the compressor when garbage is collected.
*/
protected void finalize() {
end();
}
private void ensureOpen() {
assert Thread.holdsLock(zsRef);
if (zsRef.address() == 0)
throw new NullPointerException("Deflater has been closed");
}
private static native void initIDs();
private native static long init(int level, int strategy, boolean nowrap);
private native static void setDictionary(long addr, byte[] b, int off,
int len);
private native int deflateBytes(long addr, byte[] b, int off, int len);
private native static int getAdler(long addr);
private native static long getBytesRead(long addr);
private native static long getBytesWritten(long addr);
private native static void reset(long addr);
private native static void end(long addr);
}

Your problem is not in java code.
There can be the following reasons:
the library that you are using or other library that your library depends on is not in library path (java.library.path option)
One of the native libraries you try to use does not match your platforms. For example you are running on 32 bit platform and trying to use 64bit library.
Take a look here: How to add native library to "java.library.path" with Eclipse launch (instead of overriding it)

Related

How to decode MappedByteBuffer without copying in Java

How can I decode the UTF-8 codepoints of a MappedByteBuffer in Java without copying the buffer? Copying the buffer into memory would defeat the point of mapping the memory, and since CharsetDecoder would require me to copy the buffer with the toCharArray method, it would defeat the purpose of mapping the memory. Is there any way to efficiently decode it without copying the buffer?
Here is a somewhat more complete answer.
It reads the Input-File in Chunks & stores the result in a CharBuffer.
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ChunkedUtf8Decoder {
/*
* Valid UTF-8 Multi-Bytes
* -----------------------
* 1-Byte : 0xxxxxxx
* 2-Byte : 110xxxxx + 10xxxxxx
* 3-Byte : 1110xxxx + 10xxxxxx + 10xxxxxx
* 4-Byte : 11110xxx + 10xxxxxx + 10xxxxxx + 10xxxxxx
*/
private static final int UTF8_CONTINUE_MASK = 0b11_000000;
private static final int UTF8_INVALID_MASK = 0b11111_000;
private static final int UTF8_CONTINUE_PREFIX = 0b10_000000;
private static final int UTF8_INVALID_PREFIX = UTF8_INVALID_MASK;
private static final int CHUNK_SIZE = 24; // TODO Test value! Try something larger, e.g. 64_000
private final CharsetDecoder utf8Decoder = UTF_8.newDecoder();
private final ByteBuffer bb = ByteBuffer.allocate(CHUNK_SIZE); // TODO maybe allocateDirect?
private final CharBuffer cb;
private final Path inputPath;
private byte[] remainder = {};
private int bytesChunked = 0;
public ChunkedUtf8Decoder(final Path inputPath) throws IOException {
this.inputPath = inputPath;
this.cb = CharBuffer.allocate(Math.toIntExact(Files.size(inputPath)));
// this.utf8Decoder.onMalformedInput (CodingErrorAction.REPLACE); // (TODO Default is REPORT)
// this.utf8Decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); // (TODO Default is REPORT)
}
/**
* Split the Input-File into Chunks & Decode them, appending the result to our CharBuffer.
*
* #throws IOException
*/
public void decode() throws IOException {
try(final FileChannel channel = FileChannel.open(inputPath))
{
while (channel.read(bb) != -1) {
debugBytesIn("Read......:", false);
this.remainder = backupToCharBoundary();
debugBytesIn("Backed up.:", true);
this.bytesChunked += decodeChunk();
bb.clear(); // (position=0, limit=capacity, mark=-1)
bb.put(this.remainder); // (Final Remainder is dealt with below)
}
debugBytesIn("EOF.......:", false);
/*
* Lastly, deal with Final Remainder (0 to 4 Bytes) # start of Buffer...
*/
decodeChunk();
}
}
/**
* We try to back up the BB to a Character boundary.
* If the file is correctly encoded, the shorter BB will then decode OK.
* The Remainder will be processed after the NEXT Read operation.
*
* #return
* #throws MalformedInputException
*/
private byte[] backupToCharBoundary() throws MalformedInputException {
for (int i = 1; i <= 4; i++) {
final int trailingBytePos = bb.position() - i;
if (trailingBytePos < 0) {
/*
* If there were too few Bytes available, carry them over until either
* a) more Bytes become available, or
* b) EOF is reached
*/
final byte[] remainder = new byte[bb.position()];
bb.get (0, remainder);
bb.clear(); // (position=0, limit=capacity, mark=-1)
return remainder; // (Entire contents of BB in Remainder, BB Empty)
}
final int trailingByte = 0xFF & bb.get(trailingBytePos);
/*
* We stop as soon as we encounter a Character-Selector...
* (The following 2 intervals are either invalid or a continuation-character)
*/
if ((trailingByte & UTF8_INVALID_MASK ) != UTF8_INVALID_PREFIX // 11111xxx
&& (trailingByte & UTF8_CONTINUE_MASK) != UTF8_CONTINUE_PREFIX) { // 10xxxxxx
/*
* OK, we have found a (1-, 2-, 3- or 4-Byte) Character-Selector...
*/
final byte[] remainder = new byte[i];
bb.get (trailingBytePos, remainder);
bb.position(trailingBytePos);
return remainder; // (Trailing 1-4 Bytes of BB in Remainder)
}
}
/*
* Although at least 4 Bytes are available, We couldn't find a Character-Selector!
* This is an error.
* We leave the ByteBuffer unchanged & return an empty Remainder.
* The CharsetDecoder will produce a MalformedInputException in due course...
*/
return new byte[0];
}
/**
* Decode the current Chunk of Bytes & append the result to our CB.
*
* #return
* #throws CharacterCodingException
*/
private int decodeChunk() throws CharacterCodingException {
try {
this.bb.flip(); // (limit=position, position=0, mark=-1)
; this.utf8Decoder.reset();
final CoderResult res = this.utf8Decoder.decode(this.bb, this.cb, true);
System.out.println("CB Chars..: " + this.cb.position());
/*
* Why on earth is UNDERFLOW the correct result of an error-free Decode?!
*/
if (res.isUnderflow() == false) {
res.throwException();
}
return this.bb.position();
}
catch (final MalformedInputException e) {
throw new MalformedInputException (this.bytesChunked + e.getInputLength());
}
catch (final UnmappableCharacterException e) {
throw new UnmappableCharacterException(this.bytesChunked + e.getInputLength());
/*
* (Famous Last Words "UnmappableCharacterException never occurs with UTF-8 Decoder")
*/
}
catch (final CharacterCodingException inputUnderflowOrOutputOverflow) {
throw inputUnderflowOrOutputOverflow;
/*
* (Underflow if the final Character of the final Chunk was incomplete)
* (Overflow if the Output Buffer is too small, which SHOULD NOT HAPPEN with our big CB)
*/
}
}
/**
* Some Debug Writes.
*
* #param debugText
* #param includeRemainder
*/
private void debugBytesIn(final String debugText, final boolean includeRemainder) {
System .out.print(debugText + " previous=" + this.bytesChunked + " bbPos=" + bb.position() + " Bytes.:");
for ( int p = 0; p < bb.position(); p++) {
System .out.print(" " + Integer.toHexString(0xFF & bb.get(p)));
}
if (includeRemainder) {
System .out.print(" Remainder.:");
for (int r = 0; r < this.remainder.length; r++) {
System.out.print(" " + Integer.toHexString(0xFF & this.remainder[r]));
}
}
System .out.println();
}
public static void main(final String[] args) throws IOException {
final ChunkedUtf8Decoder utf8Decoder = new ChunkedUtf8Decoder(Path.of("Utf8-Chars_nn.txt"));
; utf8Decoder.decode();
System.out.println (utf8Decoder.cb.flip()); // TODO Note.: flip()
}
}
Not really: essentially a char[] needs to be built from the byte[] (either direct or indirect) which is backing the MappedByteBuffer.
So something like the following is necessary:
private static CharBuffer readUTF8(final Path path) throws IOException {
final long byteCount = path.toFile().length();
final FileChannel channel = FileChannel.open(PATH);
final MappedByteBuffer byteBuffer = channel.map(MapMode.READ_ONLY, 0, byteCount);
return StandardCharsets.UTF_8.decode(byteBuffer);
}
The following snippet demonstrates the possibility to reuse various components used in the above example.
As stated in the Javadoc, this is a complex matter.
So it should only be considered as a collection of ideas & invocations of various JDK methods, which may or may not be of use, depending on your concrete requirements.
You really need an in-depth understanding of Charsets etc. before using the following...
/**
* It is possible to pre-allocate a CharsetDecoder & CharBuffer & use those multiple times.
* Be aware that this would make your logic MUCH more complicated.
* a) you'll have to control use/reuse & initialisation of the CharsetDecoder
* b) you'll need to ensure the CharBuffer has sufficient capacity
*
* The following is just snippets which may be of use.
*/
public static void main(final String[] args) throws Exception {
final CharsetDecoder utf8Decoder = StandardCharsets.UTF_8.newDecoder();
; utf8Decoder.onMalformedInput (CodingErrorAction.REPLACE);
; utf8Decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
final int charBufLength = 0; // TODO how long?
final CharBuffer charBuf = CharBuffer.allocate(charBufLength);
final int byteBufferLength = 0; // TODO how long?
final MappedByteBuffer byteBuffer = FileChannel.open(Path.of("my File")).map(MapMode.READ_ONLY, 0, byteBufferLength);
readUTF8(utf8Decoder, byteBuffer, charBuf);
}
private static void readUTF8(final CharsetDecoder utf8Decoder, final MappedByteBuffer byteBuffer, final CharBuffer charBuffer) throws IOException {
/* Maybe */ utf8Decoder.reset(); // TODO check out the source of the following decode(byteBuffer) method
/* Maybe */ utf8Decoder.decode(byteBuffer); // TODO check out the source of this method
utf8Decoder.decode(byteBuffer, charBuffer, true); // reuses utf8Decoder & charBuffer
charBuffer.flip();
}

Checking With Assertion

The methods setDates and setTimes have as preconditions that none of their arguments are null. This is to be checked by means of an assertion. (This means that if the precondition is not met, the program will fail at the assertion, and an AssertionError will be thrown.)
here is my code:
public class Section
{
/**
* Default section number.
*/
private static final String DEFAULT_SECTION_NUMBER = "";
/**
* Constant for building Strings with newline characters within them.
*/
private static final String LINE_SEPARATOR = System.
getProperty("line.separator");
/**
* The maximum number of students permitted into a section.
*/
private static final int MAXIMUM_STUDENTS_PER_SECTION = 30;
/**
* Valid length for a sectionNumber string.
*/
private static final int SECTION_NUMBER_LENGTH = 3;
/**
* Shared variable for keeping count of the number of section objects in
* existence.
*/
private static int count = 0;
/**
* The date at which the section is finished.
*/
private Date endDate;
/**
* The end time for the meeting of the section.
*/
private Time2 endTime;
/**
* The list of students in the class. This declaration uses the Java 7 facility
* of not repeating the generic type if that type can be inferred by the
* compiler.
*/
private final List<Student> roster = new ArrayList<>();
/**
* The three-character designation of the section (called a
* “number”).
*/
private String sectionNumber = DEFAULT_SECTION_NUMBER;
/**
* The date on which the section starts to meet.
*/
private Date startDate;
/**
* The time of day at which the section meets.
*/
private Time2 startTime;
/**
* The course of which this is a section.
*/
private final Course thisCourse;
/**
* Constructor.
*
* #param course the course of which this is a section
* #param sectionNumber the section number (within the course) of this section
* #throws SectionException
*/
public Section(Course course, String sectionNumber) throws SectionException
{
/* Increment the collective count of all Section objects that have been
created. Do this first as the object already exists. */
++count;
this.thisCourse = course;
try
{
if( isValidSectionNumber(sectionNumber) )
this.sectionNumber = sectionNumber;
}
catch (Exception ex)
{
throw new SectionException("Error in constructor", ex);
}
}
/**
* Add a student to the course.
*
* #param student the student object to be added. If the course is full, the
* student is not added
*/
public void addStudent(Student student)
{
if( roster.size() != MAXIMUM_STUDENTS_PER_SECTION )
roster.add(student);
}
/**
* Get details about the current state of this section, including the course of
* which it is part, the dates it starts and ends, times, etc., and the current
* count of the enrollment.
*
* #return the section details
*/
public String getDetails()
{
return String.join(LINE_SEPARATOR,
"Section: " + this.toString(),
"Course: " + thisCourse.getDetails(),
"Dates: " + startDate + " to " + endDate,
"Times: " + startTime + " to " + endTime,
"Enrollment: " + roster.size());
}
/**
* Create a string that represents the information about the students in the
* course.
*
* #return a string that represents the information about the students in the
* course
*/
public String getRoster()
{
/* The following commented-out code is the obvious way to do this, using
String concatenation (and this is acceptable). However, the recommended
Java approach to this kind of operation is to use a StringJoiner (new
class in Java 8), as this uses less garbage collection resources. */
// String result = "";
// for( Student student : roster )
// {
// result += ( result.isEmpty() ? "" : LINE_SEPARATOR) + student;
// }
// return result;
StringJoiner stringJoiner = new StringJoiner(LINE_SEPARATOR);
for( Student student : roster )
stringJoiner.add(student.toString());
return stringJoiner.toString();
}
/**
* Get a count of the number of students registered (on the roster) for this course.
*
* #return a count of the number of students registered for this course
*/
public int getRosterCount()
{
return roster.size();
}
/**
* Get the section number for this course.
*
* #return the section number for this course
*/
public String getSectionNumber()
{
return sectionNumber;
}
/**
* Set the start and end dates for the section.
*
* #param startDate the start date
* #param endDate the end date
*/
public void setDates(Date startDate, Date endDate)
{
/* There is no requirement to validate these. */
this.startDate = startDate;
this.endDate = endDate;
}
/**
* Set the start time and the end time for the meetings of the section.
*
* #param startTime the start time for meetings of the section
* #param endTime the end time for the meetings of the section
*/
public void setTimes(Time2 startTime, Time2 endTime)
{
/* There is no requirement to validate these. */
this.startTime = startTime;
this.endTime = endTime;
}
/**
* Section number (prefixed)
*
* #return Section number (prefixed)
*/
#Override
public String toString()
{
return thisCourse.toString() + "-" + sectionNumber;
}
/**
* Finalization. Reduce the instance count by 1.
*
* #throws Throwable standard interface.
*/
#SuppressWarnings("FinalizeDeclaration")
#Override
protected void finalize() throws Throwable
{
/* Decrement the count of the collective total of all Section objects. */
--count;
super.finalize();
}
/**
* Get a count of how many total Section objects are currently in existence.
*
* #return a count of how many Section objects are currently in existence
*/
public static int getSectionCount()
{
return count;
}
/**
* Validate the sectionNumber string. It must be of the correct length.
*
* #param sectionNumber the sectionNumber string
* #return true if the string if valid, otherwise false
*/
private static boolean isValidSectionNumber(String sectionNumber)
{
return sectionNumber != null &&
sectionNumber.length() == SECTION_NUMBER_LENGTH;
}
}
would i simply place 'assert' before this.startDate = startDate; and so forth??? my book only has one example and it is for ensuring a value is between 0 and 10.
this is the example my book uses:
public class AssertTest
{
public static void main(string[] args)
{
Scanner input = new Scanner(System.in);
System.out.print("Enter a number between 0 and 10: ");
int number = input.nextInt();
//assert that the value is >= 0 and <= 10
assert (number >= 0 && number <= 10) : "bad number: " + number;
System.out.printf("You entered %d%n", number);
}
}
so could i say
assert this.startDate = startDate
assert this.endDate = endDate
and so on?
First of all the methods setTime and setDates are public what suggests that they may be used outside of the package. Given that you have no control over parameters - using assert would not be considered as the best practice. You should rather use Runtime Exceptions such as IllegalArgumentException when value can be supplied externally (and you have no control over it):
if (startDate == null || endDate == null)
throw new IllegalArgumentException("Non-null arguments are required");
The syntax for the Assert would be as follows:
assert startDate != null;
assert endDate != null;
You can also use the following syntax in order to output additional information when assertion fails:
assert startDate != null : "startDate was set to null"
assert endDate != null : "endDate was set to null"

accent indiferent regex with jTable

I've a Java regex for a RowFilter that works as a filter for information shown in a table.
The idea is that I've a table with information in it, and below the table is a text field where I write something and filters the rows only if the row has at least on cell with a regex match based on the text entered on the text field.
I've a class that extends AbstractTableModel that's the model that I use for the table. Let's suppose that the class is called ClientesTableModel.
Then I put a event in the text field, the KeyReleased one, which does the following:
private void EventFiredInTextField() {
RowFilter<ClientesTableModel, Object> rf;
try {
rf = RowFilter.regexFilter("(?i)" + jTextFieldFiltro.getText());
} catch (PatternSyntaxException ex) {
return;
}
sorter.setRowFilter(rf);
}
Already the filter is case insensitive. Any way to make it accent insensitive?
Tried what follows already, but it doesn't works if a cell has a string with accents.
private void Filtrar() {
RowFilter<ClientesTableModel, Object> rf;
try {
rf = RowFilter.regexFilter("(?i)" + Normalizer.normalize(jTextFieldFiltro.getText(), Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));
} catch (PatternSyntaxException ex) {
return;
}
sorter.setRowFilter(rf);
}
And also tried at least 20+ solutions that appears here on SO and another sites, with no luck. None of them seem to be working with a table...
Edit 1:
Tried something more:
private void Filtrar() {
RowFilter<ClientesTableModel, Object> rf;
try {
Map<String, String> replacements = new HashMap();
replacements.put("a", "[aá]");
replacements.put("e", "[eé]");
replacements.put("i", "[ií]");
replacements.put("o", "[oó]");
replacements.put("u", "[uú]");
String regex = "";
for (char c : jTextFieldFiltro.getText().toCharArray()) {
String replacement = replacements.get(Normalizer.normalize(Character.toString(Character.toLowerCase(c)), Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));
if (replacement == null) {
regex += c;
} else {
regex += replacement;
}
}
rf = RowFilter.regexFilter("(?i)" + regex);
} catch (PatternSyntaxException ex) {
return;
}
sorter.setRowFilter(rf);
}
It just makes some changes in the regex text before it's applied, with the following idea:
If the filter text is edit it will be transformed into [eé]d[ií]t, and it will be a regex that's accent indiferent for Central American letters.
However, I'm experiencing one big problem. I've the flag (?i) in the regex so it's supposed to be case insensitive. But if a cell has for example the text edÍt, the í and Í doesn't catch the (?i) flag, they aren't treated as they should, case insensitive...
A simple solution is to change replacements.put("i", "[ií]"); by replacements.put("i", "[iíÍ]");, but hey... what's the point on putting the (?i) flag then?
Anyway, this solution isn't so elegant, and it will fail for other accent types (like ¨). Ideas?
And sorter variable is of type TableRowSorter<ClientesTableModel>.
SO doesn't allows more than 30000 characters by response, so, I'm going to split this in three answers.
Part 1/3:
After try and error for 4+ hours, found a way. Not so sexy, but it's efficient and it's a real accent indiferent solution for jTables filtering with regexes.
I'd to modify some source files of JDK version 7 update 7. They're DefaultRowSorter and TableRowSorter.
I added a extra class called RowFilterSpecialFilter for coding simplification.
The modified DefaultRowSorter and TableRowSorter classes are called DefaultRowSorterSpecialFilter and TableRowSorterSpecialFilter respectively.
DefaultRowSorterSpecialFilter and RowFilterSpecialFilter are in a package called javax.swing.
TableRowSorterSpecialFilter is in a package called javax.swing.table.
TableRowSorterSpecialFilter is basically the same as TableRowSorter. The only change is that all the ocurrences of TableRowSorter where replaced by TableRowSorterSpecialFilter, and now it inherits from DefaultRowSorterSpecialFilter. Modified file source (TableRowSorterSpecialFilter.java):
/*
* Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing.table;
import java.text.Collator;
import java.util.*;
import javax.swing.DefaultRowSorterSpecialFilter;
import javax.swing.RowFilter;
/**
* An implementation of
* <code>RowSorter</code> that provides sorting and filtering using a
* <code>TableModel</code>. The following example shows adding sorting to a
* <code>JTable</code>:
* <pre>
* TableModel myModel = createMyTableModel();
* JTable table = new JTable(myModel);
* table.setRowSorter(new TableRowSorterSpecialFilter(myModel));
* </pre> This will do all the wiring such that when the user does the
* appropriate gesture, such as clicking on the column header, the table will
* visually sort. <p>
* <code>JTable</code>'s row-based methods and
* <code>JTable</code>'s selection model refer to the view and not the
* underlying model. Therefore, it is necessary to convert between the two. For
* example, to get the selection in terms of
* <code>myModel</code> you need to convert the indices:
* <pre>
* int[] selection = table.getSelectedRows();
* for (int i = 0; i < selection.length; i++) {
* selection[i] = table.convertRowIndexToModel(selection[i]);
* }
* </pre> Similarly to select a row in
* <code>JTable</code> based on a coordinate from the underlying model do the
* inverse:
* <pre>
* table.setRowSelectionInterval(table.convertRowIndexToView(row),
* table.convertRowIndexToView(row));
* </pre> <p> The previous example assumes you have not enabled filtering. If
* you have enabled filtering
* <code>convertRowIndexToView</code> will return -1 for locations that are not
* visible in the view. <p>
* <code>TableRowSorterSpecialFilter</code> uses
* <code>Comparator</code>s for doing comparisons. The following defines how a
* <code>Comparator</code> is chosen for a column: <ol> <li>If a
* <code>Comparator</code> has been specified for the column by the
* <code>setComparator</code> method, use it. <li>If the column class as
* returned by
* <code>getColumnClass</code> is
* <code>String</code>, use the
* <code>Comparator</code> returned by
* <code>Collator.getInstance()</code>. <li>If the column class implements
* <code>Comparable</code>, use a
* <code>Comparator</code> that invokes the
* <code>compareTo</code> method. <li>If a
* <code>TableStringConverter</code> has been specified, use it to convert the
* values to
* <code>String</code>s and then use the
* <code>Comparator</code> returned by
* <code>Collator.getInstance()</code>. <li>Otherwise use the
* <code>Comparator</code> returned by
* <code>Collator.getInstance()</code> on the results from calling
* <code>toString</code> on the objects. </ol> <p> In addition to sorting
* <code>TableRowSorterSpecialFilter</code> provides the ability to filter. A
* filter is specified using the
* <code>setFilter</code> method. The following example will only show rows
* containing the string "foo":
* <pre>
* TableModel myModel = createMyTableModel();
* TableRowSorterSpecialFilter sorter = new TableRowSorterSpecialFilter(myModel);
* sorter.setRowFilter(RowFilter.regexFilter(".*foo.*"));
* JTable table = new JTable(myModel);
* table.setRowSorter(sorter);
* </pre> <p> If the underlying model structure changes (the
* <code>modelStructureChanged</code> method is invoked) the following are reset
* to their default values:
* <code>Comparator</code>s by column, current sort order, and whether each
* column is sortable. The default sort order is natural (the same as the
* model), and columns are sortable by default. <p>
* <code>TableRowSorterSpecialFilter</code> has one formal type parameter: the
* type of the model. Passing in a type that corresponds exactly to your model
* allows you to filter based on your model without casting. Refer to the
* documentation of
* <code>RowFilter</code> for an example of this. <p> <b>WARNING:</b>
* <code>DefaultTableModel</code> returns a column class of
* <code>Object</code>. As such all comparisons will be done using
* <code>toString</code>. This may be unnecessarily expensive. If the column
* only contains one type of value, such as an
* <code>Integer</code>, you should override
* <code>getColumnClass</code> and return the appropriate
* <code>Class</code>. This will dramatically increase the performance of this
* class.
*
* #param <M> the type of the model, which must be an implementation of
* <code>TableModel</code>
* #see javax.swing.JTable
* #see javax.swing.RowFilter
* #see javax.swing.table.DefaultTableModel
* #see java.text.Collator
* #see java.util.Comparator
* #since 1.6
*/
public final class TableRowSorterSpecialFilter<M extends TableModel> extends DefaultRowSorterSpecialFilter<M, Integer> {
/**
* Comparator that uses compareTo on the contents.
*/
private static final Comparator COMPARABLE_COMPARATOR =
new ComparableComparator();
/**
* Underlying model.
*/
private M tableModel;
/**
* For toString conversions.
*/
private TableStringConverter stringConverter;
/**
* Creates a
* <code>TableRowSorterSpecialFilter</code> with an empty model.
*/
public TableRowSorterSpecialFilter() {
this(null);
}
/**
* Creates a
* <code>TableRowSorterSpecialFilter</code> using
* <code>model</code> as the underlying
* <code>TableModel</code>.
*
* #param model the underlying <code>TableModel</code> to use,
* <code>null</code> is treated as an empty model
*/
public TableRowSorterSpecialFilter(M model) {
setModel(model);
}
/**
* Sets the
* <code>TableModel</code> to use as the underlying model for this
* <code>TableRowSorterSpecialFilter</code>. A value of
* <code>null</code> can be used to set an empty model.
*
* #param model the underlying model to use, or <code>null</code>
*/
public void setModel(M model) {
tableModel = model;
setModelWrapper(new TableRowSorterModelWrapper());
}
/**
* Sets the object responsible for converting values from the model to
* strings. If non-
* <code>null</code> this is used to convert any object values, that do not
* have a registered
* <code>Comparator</code>, to strings.
*
* #param stringConverter the object responsible for converting values from
* the model to strings
*/
public void setStringConverter(TableStringConverter stringConverter) {
this.stringConverter = stringConverter;
}
/**
* Returns the object responsible for converting values from the model to
* strings.
*
* #return object responsible for converting values to strings.
*/
public TableStringConverter getStringConverter() {
return stringConverter;
}
/**
* Returns the
* <code>Comparator</code> for the specified column. If a
* <code>Comparator</code> has not been specified using the
* <code>setComparator</code> method a
* <code>Comparator</code> will be returned based on the column class
* (
* <code>TableModel.getColumnClass</code>) of the specified column. If the
* column class is
* <code>String</code>,
* <code>Collator.getInstance</code> is returned. If the column class
* implements
* <code>Comparable</code> a private
* <code>Comparator</code> is returned that invokes the
* <code>compareTo</code> method. Otherwise
* <code>Collator.getInstance</code> is returned.
*
* #throws IndexOutOfBoundsException {#inheritDoc}
*/
#Override
public Comparator<?> getComparator(int column) {
Comparator comparator = super.getComparator(column);
if (comparator != null) {
return comparator;
}
Class columnClass = getModel().getColumnClass(column);
if (columnClass == String.class) {
return Collator.getInstance();
}
if (Comparable.class.isAssignableFrom(columnClass)) {
return COMPARABLE_COMPARATOR;
}
return Collator.getInstance();
}
/**
* {#inheritDoc}
*
* #throws IndexOutOfBoundsException {#inheritDoc}
*/
#Override
protected boolean useToString(int column) {
Comparator comparator = super.getComparator(column);
if (comparator != null) {
return false;
}
Class columnClass = getModel().getColumnClass(column);
if (columnClass == String.class) {
return false;
}
if (Comparable.class.isAssignableFrom(columnClass)) {
return false;
}
return true;
}
/**
* Implementation of DefaultRowSorterSpecialFilter.ModelWrapper that
* delegates to a TableModel.
*/
private class TableRowSorterModelWrapper extends ModelWrapper<M, Integer> {
#Override
public M getModel() {
return tableModel;
}
#Override
public int getColumnCount() {
return (tableModel == null) ? 0 : tableModel.getColumnCount();
}
#Override
public int getRowCount() {
return (tableModel == null) ? 0 : tableModel.getRowCount();
}
#Override
public Object getValueAt(int row, int column) {
return tableModel.getValueAt(row, column);
}
#Override
public String getStringValueAt(int row, int column) {
TableStringConverter converter = getStringConverter();
if (converter != null) {
// Use the converter
String value = converter.toString(
tableModel, row, column);
if (value != null) {
return value;
}
return "";
}
// No converter, use getValueAt followed by toString
Object o = getValueAt(row, column);
if (o == null) {
return "";
}
String string = o.toString();
if (string == null) {
return "";
}
return string;
}
#Override
public Integer getIdentifier(int index) {
return index;
}
}
private static class ComparableComparator implements Comparator {
#SuppressWarnings("unchecked")
#Override
public int compare(Object o1, Object o2) {
return ((Comparable) o1).compareTo(o2);
}
}
}
In the case of DefaultRowSorterSpecialFilter, it has more changes than TableRowSorterSpecialFilter. Basically it has a extra property, public boolean accentIndiferent which starts in false and the overrided method public String getStringValue(int index) of the nested class private class FilterEntry extends RowFilter.Entry<M, I> has been modified to return a string without accents based on the accentIndiferent value. Modified source (DefaultRowSorterSpecialFilter.java):
SO doesn't allows more than 30000 characters by response, so, I'm going to split this in three answers.
Part 2/3:
/*
* Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing;
import java.text.Collator;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* An implementation of
* <code>RowSorter</code> that provides sorting and filtering around a
* grid-based data model. Beyond creating and installing a
* <code>RowSorter</code>, you very rarely need to interact with one directly.
* Refer to {#link javax.swing.table.TableRowSorter TableRowSorter} for a
* concrete implementation of
* <code>RowSorter</code> for
* <code>JTable</code>. <p> Sorting is done based on the current
* <code>SortKey</code>s, in order. If two objects are equal (the
* <code>Comparator</code> for the column returns 0) the next
* <code>SortKey</code> is used. If no
* <code>SortKey</code>s remain or the order is
* <code>UNSORTED</code>, then the order of the rows in the model is used. <p>
* Sorting of each column is done by way of a
* <code>Comparator</code> that you can specify using the
* <code>setComparator</code> method. If a
* <code>Comparator</code> has not been specified, the
* <code>Comparator</code> returned by
* <code>Collator.getInstance()</code> is used on the results of calling
* <code>toString</code> on the underlying objects. The
* <code>Comparator</code> is never passed
* <code>null</code>. A
* <code>null</code> value is treated as occuring before a non-
* <code>null</code> value, and two
* <code>null</code> values are considered equal. <p> If you specify a
* <code>Comparator</code> that casts its argument to a type other than that
* provided by the model, a
* <code>ClassCastException</code> will be thrown when the data is sorted. <p>
* In addition to sorting,
* <code>DefaultRowSorterSpecialFilter</code> provides the ability to
* filterInclude rows. Filtering is done by way of a
* <code>RowFilter</code> that is specified using the
* <code>setRowFilter</code> method. If no filterInclude has been specified all
* rows are included. <p> By default, rows are in unsorted order (the same as
* the model) and every column is sortable. The default
* <code>Comparator</code>s are documented in the subclasses (for example, {#link
* javax.swing.table.TableRowSorter TableRowSorter}). <p> If the underlying
* model structure changes (the
* <code>modelStructureChanged</code> method is invoked) the following are reset
* to their default values:
* <code>Comparator</code>s by column, current sort order, and whether each
* column is sortable. To find the default
* <code>Comparator</code>s, see the concrete implementation (for example, {#link
* javax.swing.table.TableRowSorter TableRowSorter}). The default sort order is
* unsorted (the same as the model), and columns are sortable by default. <p> If
* the underlying model structure changes (the
* <code>modelStructureChanged</code> method is invoked) the following are reset
* to their default values:
* <code>Comparator</code>s by column, current sort order and whether a column
* is sortable. <p>
* <code>DefaultRowSorterSpecialFilter</code> is an abstract class. Concrete
* subclasses must provide access to the underlying data by invoking
* {#code setModelWrapper}. The {#code setModelWrapper} method <b>must</b> be
* invoked soon after the constructor is called, ideally from within the
* subclass's constructor. Undefined behavior will result if you use a {#code
* DefaultRowSorterSpecialFilter} without specifying a {#code ModelWrapper}. <p>
* <code>DefaultRowSorterSpecialFilter</code> has two formal type parameters.
* The first type parameter corresponds to the class of the model, for example
* <code>DefaultTableModel</code>. The second type parameter corresponds to the
* class of the identifier passed to the
* <code>RowFilter</code>. Refer to
* <code>TableRowSorter</code> and
* <code>RowFilter</code> for more details on the type parameters.
*
* #param <M> the type of the model
* #param <I> the type of the identifier passed to the <code>RowFilter</code>
* #see javax.swing.table.TableRowSorter
* #see javax.swing.table.DefaultTableModel
* #see java.text.Collator
* #since 1.6
*/
public abstract class DefaultRowSorterSpecialFilter<M, I> extends RowSorter<M> {
public boolean accentIndiferent = false;
/**
* Whether or not we resort on TableModelEvent.UPDATEs.
*/
private boolean sortsOnUpdates;
/**
* View (JTable) -> model.
*/
private Row[] viewToModel;
/**
* model -> view (JTable)
*/
private int[] modelToView;
/**
* Comparators specified by column.
*/
private Comparator[] comparators;
/**
* Whether or not the specified column is sortable, by column.
*/
private boolean[] isSortable;
/**
* Cached SortKeys for the current sort.
*/
private SortKey[] cachedSortKeys;
/**
* Cached comparators for the current sort
*/
private Comparator[] sortComparators;
/**
* Developer supplied Filter.
*/
private RowFilter<? super M, ? super I> filter;
/**
* Value passed to the filterInclude. The same instance is passed to the
* filterInclude for different rows.
*/
private FilterEntry filterEntry;
/**
* The sort keys.
*/
private List<SortKey> sortKeys;
/**
* Whether or not to use getStringValueAt. This is indexed by column.
*/
private boolean[] useToString;
/**
* Indicates the contents are sorted. This is used if getSortsOnUpdates is
* false and an update event is received.
*/
private boolean sorted;
/**
* Maximum number of sort keys.
*/
private int maxSortKeys;
/**
* Provides access to the data we're sorting/filtering.
*/
private ModelWrapper<M, I> modelWrapper;
/**
* Size of the model. This is used to enforce error checking within the
* table changed notification methods (such as rowsInserted).
*/
private int modelRowCount;
/**
* Creates an empty
* <code>DefaultRowSorterSpecialFilter</code>.
*/
public DefaultRowSorterSpecialFilter() {
sortKeys = Collections.emptyList();
maxSortKeys = 3;
}
/**
* Sets the model wrapper providing the data that is being sorted and
* filtered.
*
* #param modelWrapper the model wrapper responsible for providing the data
* that gets sorted and filtered
* #throws IllegalArgumentException if {#code modelWrapper} is {#code null}
*/
protected final void setModelWrapper(ModelWrapper<M, I> modelWrapper) {
if (modelWrapper == null) {
throw new IllegalArgumentException(
"modelWrapper most be non-null");
}
ModelWrapper<M, I> last = this.modelWrapper;
this.modelWrapper = modelWrapper;
if (last != null) {
modelStructureChanged();
} else {
// If last is null, we're in the constructor. If we're in
// the constructor we don't want to call to overridable methods.
modelRowCount = getModelWrapper().getRowCount();
}
}
/**
* Returns the model wrapper providing the data that is being sorted and
* filtered.
*
* #return the model wrapper responsible for providing the data that gets
* sorted and filtered
*/
protected final ModelWrapper<M, I> getModelWrapper() {
return modelWrapper;
}
/**
* Returns the underlying model.
*
* #return the underlying model
*/
#Override
public final M getModel() {
return getModelWrapper().getModel();
}
/**
* Sets whether or not the specified column is sortable. The specified value
* is only checked when
* <code>toggleSortOrder</code> is invoked. It is still possible to sort on
* a column that has been marked as unsortable by directly setting the sort
* keys. The default is true.
*
* #param column the column to enable or disable sorting on, in terms of the
* underlying model
* #param sortable whether or not the specified column is sortable
* #throws IndexOutOfBoundsException if <code>column</code> is outside the
* range of the model
* #see #toggleSortOrder
* #see #setSortKeys
*/
public void setSortable(int column, boolean sortable) {
checkColumn(column);
if (isSortable == null) {
isSortable = new boolean[getModelWrapper().getColumnCount()];
for (int i = isSortable.length - 1; i >= 0; i--) {
isSortable[i] = true;
}
}
isSortable[column] = sortable;
}
/**
* Returns true if the specified column is sortable; otherwise, false.
*
* #param column the column to check sorting for, in terms of the underlying
* model
* #return true if the column is sortable
* #throws IndexOutOfBoundsException if column is outside the range of the
* underlying model
*/
public boolean isSortable(int column) {
checkColumn(column);
return (isSortable == null) ? true : isSortable[column];
}
/**
* Sets the sort keys. This creates a copy of the supplied {#code List};
* subsequent changes to the supplied {#code List} do not effect this
* {#code DefaultRowSorterSpecialFilter}. If the sort keys have changed this
* triggers a sort.
*
* #param sortKeys the new <code>SortKeys</code>; <code>null</code> is a
* shorthand for specifying an empty list, indicating that the view should
* be unsorted
* #throws IllegalArgumentException if any of the values in
* <code>sortKeys</code> are null or have a column index outside the range
* of the model
*/
#Override
public void setSortKeys(List<? extends SortKey> sortKeys) {
List<SortKey> old = this.sortKeys;
if (sortKeys != null && sortKeys.size() > 0) {
int max = getModelWrapper().getColumnCount();
for (SortKey key : sortKeys) {
if (key == null || key.getColumn() < 0
|| key.getColumn() >= max) {
throw new IllegalArgumentException("Invalid SortKey");
}
}
this.sortKeys = Collections.unmodifiableList(
new ArrayList<>(sortKeys));
} else {
this.sortKeys = Collections.emptyList();
}
if (!this.sortKeys.equals(old)) {
fireSortOrderChanged();
if (viewToModel == null) {
// Currently unsorted, use sort so that internal fields
// are correctly set.
sort();
} else {
sortExistingData();
}
}
}
/**
* Returns the current sort keys. This returns an unmodifiable
* {#code non-null List}. If you need to change the sort keys, make a copy
* of the returned {#code List}, mutate the copy and invoke
* {#code setSortKeys} with the new list.
*
* #return the current sort order
*/
#Override
public List<? extends SortKey> getSortKeys() {
return sortKeys;
}
/**
* Sets the maximum number of sort keys. The number of sort keys determines
* how equal values are resolved when sorting. For example, assume a table
* row sorter is created and
* <code>setMaxSortKeys(2)</code> is invoked on it. The user clicks the
* header for column 1, causing the table rows to be sorted based on the
* items in column 1. Next, the user clicks the header for column 2, causing
* the table to be sorted based on the items in column 2; if any items in
* column 2 are equal, then those particular rows are ordered based on the
* items in column 1. In this case, we say that the rows are primarily
* sorted on column 2, and secondarily on column 1. If the user then clicks
* the header for column 3, then the items are primarily sorted on column 3
* and secondarily sorted on column 2. Because the maximum number of sort
* keys has been set to 2 with
* <code>setMaxSortKeys</code>, column 1 no longer has an effect on the
* order. <p> The maximum number of sort keys is enforced by
* <code>toggleSortOrder</code>. You can specify more sort keys by invoking
* <code>setSortKeys</code> directly and they will all be honored. However
* if
* <code>toggleSortOrder</code> is subsequently invoked the maximum number
* of sort keys will be enforced. The default value is 3.
*
* #param max the maximum number of sort keys
* #throws IllegalArgumentException if <code>max</code> < 1
*/
public void setMaxSortKeys(int max) {
if (max < 1) {
throw new IllegalArgumentException("Invalid max");
}
maxSortKeys = max;
}
/**
* Returns the maximum number of sort keys.
*
* #return the maximum number of sort keys
*/
public int getMaxSortKeys() {
return maxSortKeys;
}
/**
* If true, specifies that a sort should happen when the underlying model is
* updated (
* <code>rowsUpdated</code> is invoked). For example, if this is true and
* the user edits an entry the location of that item in the view may change.
* The default is false.
*
* #param sortsOnUpdates whether or not to sort on update events
*/
public void setSortsOnUpdates(boolean sortsOnUpdates) {
this.sortsOnUpdates = sortsOnUpdates;
}
/**
* Returns true if a sort should happen when the underlying model is
* updated; otherwise, returns false.
*
* #return whether or not to sort when the model is updated
*/
public boolean getSortsOnUpdates() {
return sortsOnUpdates;
}
/**
* Sets the filterInclude that determines which rows, if any, should be
* hidden from the view. The filterInclude is applied before sorting. A
* value of
* <code>null</code> indicates all values from the model should be included.
* <p>
* <code>RowFilter</code>'s
* <code>include</code> method is passed an
* <code>Entry</code> that wraps the underlying model. The number of columns
* in the
* <code>Entry</code> corresponds to the number of columns in the
* <code>ModelWrapper</code>. The identifier comes from the
* <code>ModelWrapper</code> as well. <p> This method triggers a sort.
*
* #param filterInclude the filterInclude used to determine what entries
* should be included
*/
public void setRowFilter(RowFilter<? super M, ? super I> filter) {
this.filter = filter;
sort();
}
/**
* Returns the filterInclude that determines which rows, if any, should be
* hidden from view.
*
* #return the filterInclude
*/
public RowFilter<? super M, ? super I> getRowFilter() {
return filter;
}
/**
* Reverses the sort order from ascending to descending (or descending to
* ascending) if the specified column is already the primary sorted column;
* otherwise, makes the specified column the primary sorted column, with an
* ascending sort order. If the specified column is not sortable, this
* method has no effect.
*
* #param column index of the column to make the primary sorted column, in
* terms of the underlying model
* #throws IndexOutOfBoundsException {#inheritDoc}
* #see #setSortable(int,boolean)
* #see #setMaxSortKeys(int)
*/
#Override
public void toggleSortOrder(int column) {
checkColumn(column);
if (isSortable(column)) {
List<SortKey> keys = new ArrayList<>(getSortKeys());
SortKey sortKey;
int sortIndex;
for (sortIndex = keys.size() - 1; sortIndex >= 0; sortIndex--) {
if (keys.get(sortIndex).getColumn() == column) {
break;
}
}
if (sortIndex == -1) {
// Key doesn't exist
sortKey = new SortKey(column, SortOrder.ASCENDING);
keys.add(0, sortKey);
} else if (sortIndex == 0) {
// It's the primary sorting key, toggle it
keys.set(0, toggle(keys.get(0)));
} else {
// It's not the first, but was sorted on, remove old
// entry, insert as first with ascending.
keys.remove(sortIndex);
keys.add(0, new SortKey(column, SortOrder.ASCENDING));
}
if (keys.size() > getMaxSortKeys()) {
keys = keys.subList(0, getMaxSortKeys());
}
setSortKeys(keys);
}
}
private SortKey toggle(SortKey key) {
if (key.getSortOrder() == SortOrder.ASCENDING) {
return new SortKey(key.getColumn(), SortOrder.DESCENDING);
}
return new SortKey(key.getColumn(), SortOrder.ASCENDING);
}
/**
* {#inheritDoc}
*
* #throws IndexOutOfBoundsException {#inheritDoc}
*/
#Override
public int convertRowIndexToView(int index) {
if (modelToView == null) {
if (index < 0 || index >= getModelWrapper().getRowCount()) {
throw new IndexOutOfBoundsException("Invalid index");
}
return index;
}
return modelToView[index];
}
/**
* {#inheritDoc}
*
* #throws IndexOutOfBoundsException {#inheritDoc}
*/
#Override
public int convertRowIndexToModel(int index) {
if (viewToModel == null) {
if (index < 0 || index >= getModelWrapper().getRowCount()) {
throw new IndexOutOfBoundsException("Invalid index");
}
return index;
}
return viewToModel[index].modelIndex;
}
private boolean isUnsorted() {
List<? extends SortKey> keys = getSortKeys();
int keySize = keys.size();
return (keySize == 0 || keys.get(0).getSortOrder()
== SortOrder.UNSORTED);
}
/**
* Sorts the existing filtered data. This should only be used if the
* filterInclude hasn't changed.
*/
private void sortExistingData() {
int[] lastViewToModel = getViewToModelAsInts(viewToModel);
updateUseToString();
cacheSortKeys(getSortKeys());
if (isUnsorted()) {
if (getRowFilter() == null) {
viewToModel = null;
modelToView = null;
} else {
int included = 0;
for (int i = 0; i < modelToView.length; i++) {
if (modelToView[i] != -1) {
viewToModel[included].modelIndex = i;
modelToView[i] = included++;
}
}
}
} else {
// sort the data
Arrays.sort(viewToModel);
// Update the modelToView array
setModelToViewFromViewToModel(false);
}
fireRowSorterChanged(lastViewToModel);
}
/**
* Sorts and filters the rows in the view based on the sort keys of the
* columns currently being sorted and the filterInclude, if any, associated
* with this sorter. An empty
* <code>sortKeys</code> list indicates that the view should unsorted, the
* same as the model.
*
* #see #setRowFilter
* #see #setSortKeys
*/
public void sort() {
sorted = true;
int[] lastViewToModel = getViewToModelAsInts(viewToModel);
updateUseToString();
if (isUnsorted()) {
// Unsorted
cachedSortKeys = new SortKey[0];
if (getRowFilter() == null) {
// No filterInclude & unsorted
if (viewToModel != null) {
// sorted -> unsorted
viewToModel = null;
modelToView = null;
} else {
// unsorted -> unsorted
// No need to do anything.
return;
}
} else {
// There is filterInclude, reset mappings
initializeFilteredMapping();
}
} else {
cacheSortKeys(getSortKeys());
if (getRowFilter() != null) {
initializeFilteredMapping();
} else {
createModelToView(getModelWrapper().getRowCount());
createViewToModel(getModelWrapper().getRowCount());
}
// sort them
Arrays.sort(viewToModel);
// Update the modelToView array
setModelToViewFromViewToModel(false);
}
fireRowSorterChanged(lastViewToModel);
}
/**
* Updates the useToString mapping before a sort.
*/
private void updateUseToString() {
int i = getModelWrapper().getColumnCount();
if (useToString == null || useToString.length != i) {
useToString = new boolean[i];
}
for (--i; i >= 0; i--) {
useToString[i] = useToString(i);
}
}
/**
* Resets the viewToModel and modelToView mappings based on the current
* Filter.
*/
private void initializeFilteredMapping() {
int rowCount = getModelWrapper().getRowCount();
int i, j;
int excludedCount = 0;
// Update model -> view
createModelToView(rowCount);
for (i = 0; i < rowCount; i++) {
if (include(i)) {
modelToView[i] = i - excludedCount;
} else {
modelToView[i] = -1;
excludedCount++;
}
}
// Update view -> model
createViewToModel(rowCount - excludedCount);
for (i = 0, j = 0; i < rowCount; i++) {
if (modelToView[i] != -1) {
viewToModel[j++].modelIndex = i;
}
}
}
/**
* Makes sure the modelToView array is of size rowCount.
*/
private void createModelToView(int rowCount) {
if (modelToView == null || modelToView.length != rowCount) {
modelToView = new int[rowCount];
}
}
/**
* Resets the viewToModel array to be of size rowCount.
*/
private void createViewToModel(int rowCount) {
int recreateFrom = 0;
if (viewToModel != null) {
recreateFrom = Math.min(rowCount, viewToModel.length);
if (viewToModel.length != rowCount) {
Row[] oldViewToModel = viewToModel;
viewToModel = new Row[rowCount];
System.arraycopy(oldViewToModel, 0, viewToModel,
0, recreateFrom);
}
} else {
viewToModel = new Row[rowCount];
}
int i;
for (i = 0; i < recreateFrom; i++) {
viewToModel[i].modelIndex = i;
}
for (i = recreateFrom; i < rowCount; i++) {
viewToModel[i] = new Row(this, i);
}
}
/**
* Caches the sort keys before a sort.
*/
private void cacheSortKeys(List<? extends SortKey> keys) {
int keySize = keys.size();
sortComparators = new Comparator[keySize];
for (int i = 0; i < keySize; i++) {
sortComparators[i] = getComparator0(keys.get(i).getColumn());
}
cachedSortKeys = keys.toArray(new SortKey[keySize]);
}
/**
* Returns whether or not to convert the value to a string before doing
* comparisons when sorting. If true
* <code>ModelWrapper.getStringValueAt</code> will be used, otherwise
* <code>ModelWrapper.getValueAt</code> will be used. It is up to
* subclasses, such as
* <code>TableRowSorter</code>, to honor this value in their
* <code>ModelWrapper</code> implementation.
*
* #param column the index of the column to test, in terms of the underlying
SO doesn't allows more than 30000 characters by response, so, I'm going to split this in three answers.
Part 3/3:
* model
* #throws IndexOutOfBoundsException if <code>column</code> is not valid
*/
protected boolean useToString(int column) {
return (getComparator(column) == null);
}
/**
* Refreshes the modelToView mapping from that of viewToModel. If
* <code>unsetFirst</code> is true, all indices in modelToView are first set
* to -1.
*/
private void setModelToViewFromViewToModel(boolean unsetFirst) {
int i;
if (unsetFirst) {
for (i = modelToView.length - 1; i >= 0; i--) {
modelToView[i] = -1;
}
}
for (i = viewToModel.length - 1; i >= 0; i--) {
modelToView[viewToModel[i].modelIndex] = i;
}
}
private int[] getViewToModelAsInts(Row[] viewToModel) {
if (viewToModel != null) {
int[] viewToModelI = new int[viewToModel.length];
for (int i = viewToModel.length - 1; i >= 0; i--) {
viewToModelI[i] = viewToModel[i].modelIndex;
}
return viewToModelI;
}
return new int[0];
}
/**
* Sets the
* <code>Comparator</code> to use when sorting the specified column. This
* does not trigger a sort. If you want to sort after setting the comparator
* you need to explicitly invoke
* <code>sort</code>.
*
* #param column the index of the column the <code>Comparator</code> is to
* be used for, in terms of the underlying model
* #param comparator the <code>Comparator</code> to use
* #throws IndexOutOfBoundsException if <code>column</code> is outside the
* range of the underlying model
*/
public void setComparator(int column, Comparator<?> comparator) {
checkColumn(column);
if (comparators == null) {
comparators = new Comparator[getModelWrapper().getColumnCount()];
}
comparators[column] = comparator;
}
/**
* Returns the
* <code>Comparator</code> for the specified column. This will return
* <code>null</code> if a
* <code>Comparator</code> has not been specified for the column.
*
* #param column the column to fetch the <code>Comparator</code> for, in
* terms of the underlying model
* #return the <code>Comparator</code> for the specified column
* #throws IndexOutOfBoundsException if column is outside the range of the
* underlying model
*/
public Comparator<?> getComparator(int column) {
checkColumn(column);
if (comparators != null) {
return comparators[column];
}
return null;
}
// Returns the Comparator to use during sorting. Where as
// getComparator() may return null, this will never return null.
private Comparator getComparator0(int column) {
Comparator comparator = getComparator(column);
if (comparator != null) {
return comparator;
}
// This should be ok as useToString(column) should have returned
// true in this case.
return Collator.getInstance();
}
private RowFilter.Entry<M, I> getFilterEntry(int modelIndex) {
if (filterEntry == null) {
filterEntry = new FilterEntry();
}
filterEntry.modelIndex = modelIndex;
return filterEntry;
}
/**
* {#inheritDoc}
*/
#Override
public int getViewRowCount() {
if (viewToModel != null) {
// When filtering this may differ from getModelWrapper().getRowCount()
return viewToModel.length;
}
return getModelWrapper().getRowCount();
}
/**
* {#inheritDoc}
*/
#Override
public int getModelRowCount() {
return getModelWrapper().getRowCount();
}
private void allChanged() {
modelToView = null;
viewToModel = null;
comparators = null;
isSortable = null;
if (isUnsorted()) {
// Keys are already empty, to force a resort we have to
// call sort
sort();
} else {
setSortKeys(null);
}
}
/**
* {#inheritDoc}
*/
#Override
public void modelStructureChanged() {
allChanged();
modelRowCount = getModelWrapper().getRowCount();
}
/**
* {#inheritDoc}
*/
#Override
public void allRowsChanged() {
modelRowCount = getModelWrapper().getRowCount();
sort();
}
/**
* {#inheritDoc}
*
* #throws IndexOutOfBoundsException {#inheritDoc}
*/
#Override
public void rowsInserted(int firstRow, int endRow) {
checkAgainstModel(firstRow, endRow);
int newModelRowCount = getModelWrapper().getRowCount();
if (endRow >= newModelRowCount) {
throw new IndexOutOfBoundsException("Invalid range");
}
modelRowCount = newModelRowCount;
if (shouldOptimizeChange(firstRow, endRow)) {
rowsInserted0(firstRow, endRow);
}
}
/**
* {#inheritDoc}
*
* #throws IndexOutOfBoundsException {#inheritDoc}
*/
#Override
public void rowsDeleted(int firstRow, int endRow) {
checkAgainstModel(firstRow, endRow);
if (firstRow >= modelRowCount || endRow >= modelRowCount) {
throw new IndexOutOfBoundsException("Invalid range");
}
modelRowCount = getModelWrapper().getRowCount();
if (shouldOptimizeChange(firstRow, endRow)) {
rowsDeleted0(firstRow, endRow);
}
}
/**
* {#inheritDoc}
*
* #throws IndexOutOfBoundsException {#inheritDoc}
*/
#Override
public void rowsUpdated(int firstRow, int endRow) {
checkAgainstModel(firstRow, endRow);
if (firstRow >= modelRowCount || endRow >= modelRowCount) {
throw new IndexOutOfBoundsException("Invalid range");
}
if (getSortsOnUpdates()) {
if (shouldOptimizeChange(firstRow, endRow)) {
rowsUpdated0(firstRow, endRow);
}
} else {
sorted = false;
}
}
/**
* {#inheritDoc}
*
* #throws IndexOutOfBoundsException {#inheritDoc}
*/
#Override
public void rowsUpdated(int firstRow, int endRow, int column) {
checkColumn(column);
rowsUpdated(firstRow, endRow);
}
private void checkAgainstModel(int firstRow, int endRow) {
if (firstRow > endRow || firstRow < 0 || endRow < 0
|| firstRow > modelRowCount) {
throw new IndexOutOfBoundsException("Invalid range");
}
}
/**
* Returns true if the specified row should be included.
*/
private boolean include(int row) {
RowFilter<? super M, ? super I> filterInclude = getRowFilter();
if (filterInclude != null) {
return filterInclude.include(getFilterEntry(row));
}
// null filterInclude, always include the row.
return true;
}
#SuppressWarnings("unchecked")
private int compare(int model1, int model2) {
int column;
SortOrder sortOrder;
Object v1, v2;
int result;
for (int counter = 0; counter < cachedSortKeys.length; counter++) {
column = cachedSortKeys[counter].getColumn();
sortOrder = cachedSortKeys[counter].getSortOrder();
if (sortOrder == SortOrder.UNSORTED) {
result = model1 - model2;
} else {
// v1 != null && v2 != null
if (useToString[column]) {
v1 = getModelWrapper().getStringValueAt(model1, column);
v2 = getModelWrapper().getStringValueAt(model2, column);
} else {
v1 = getModelWrapper().getValueAt(model1, column);
v2 = getModelWrapper().getValueAt(model2, column);
}
// Treat nulls as < then non-null
if (v1 == null) {
if (v2 == null) {
result = 0;
} else {
result = -1;
}
} else if (v2 == null) {
result = 1;
} else {
result = sortComparators[counter].compare(v1, v2);
}
if (sortOrder == SortOrder.DESCENDING) {
result *= -1;
}
}
if (result != 0) {
return result;
}
}
// If we get here, they're equal. Fallback to model order.
return model1 - model2;
}
/**
* Whether not we are filtering/sorting.
*/
private boolean isTransformed() {
return (viewToModel != null);
}
/**
* Insets new set of entries.
*
* #param toAdd the Rows to add, sorted
* #param current the array to insert the items into
*/
private void insertInOrder(List<Row> toAdd, Row[] current) {
int last = 0;
int index;
int max = toAdd.size();
for (int i = 0; i < max; i++) {
index = Arrays.binarySearch(current, toAdd.get(i));
if (index < 0) {
index = -1 - index;
}
System.arraycopy(current, last,
viewToModel, last + i, index - last);
viewToModel[index + i] = toAdd.get(i);
last = index;
}
System.arraycopy(current, last, viewToModel, last + max,
current.length - last);
}
/**
* Returns true if we should try and optimize the processing of the
* <code>TableModelEvent</code>. If this returns false, assume the event was
* dealt with and no further processing needs to happen.
*/
private boolean shouldOptimizeChange(int firstRow, int lastRow) {
if (!isTransformed()) {
// Not transformed, nothing to do.
return false;
}
if (!sorted || (lastRow - firstRow) > viewToModel.length / 10) {
// We either weren't sorted, or to much changed, sort it all
sort();
return false;
}
return true;
}
private void rowsInserted0(int firstRow, int lastRow) {
int[] oldViewToModel = getViewToModelAsInts(viewToModel);
int i;
int delta = (lastRow - firstRow) + 1;
List<Row> added = new ArrayList<>(delta);
// Build the list of Rows to add into added
for (i = firstRow; i <= lastRow; i++) {
if (include(i)) {
added.add(new Row(this, i));
}
}
// Adjust the model index of rows after the effected region
int viewIndex;
for (i = modelToView.length - 1; i >= firstRow; i--) {
viewIndex = modelToView[i];
if (viewIndex != -1) {
viewToModel[viewIndex].modelIndex += delta;
}
}
// Insert newly added rows into viewToModel
if (added.size() > 0) {
Collections.sort(added);
Row[] lastViewToModel = viewToModel;
viewToModel = new Row[viewToModel.length + added.size()];
insertInOrder(added, lastViewToModel);
}
// Update modelToView
createModelToView(getModelWrapper().getRowCount());
setModelToViewFromViewToModel(true);
// Notify of change
fireRowSorterChanged(oldViewToModel);
}
private void rowsDeleted0(int firstRow, int lastRow) {
int[] oldViewToModel = getViewToModelAsInts(viewToModel);
int removedFromView = 0;
int i;
int viewIndex;
// Figure out how many visible rows are going to be effected.
for (i = firstRow; i <= lastRow; i++) {
viewIndex = modelToView[i];
if (viewIndex != -1) {
removedFromView++;
viewToModel[viewIndex] = null;
}
}
// Update the model index of rows after the effected region
int delta = lastRow - firstRow + 1;
for (i = modelToView.length - 1; i > lastRow; i--) {
viewIndex = modelToView[i];
if (viewIndex != -1) {
viewToModel[viewIndex].modelIndex -= delta;
}
}
// Then patch up the viewToModel array
if (removedFromView > 0) {
Row[] newViewToModel = new Row[viewToModel.length
- removedFromView];
int newIndex = 0;
int last = 0;
for (i = 0; i < viewToModel.length; i++) {
if (viewToModel[i] == null) {
System.arraycopy(viewToModel, last,
newViewToModel, newIndex, i - last);
newIndex += (i - last);
last = i + 1;
}
}
System.arraycopy(viewToModel, last,
newViewToModel, newIndex, viewToModel.length - last);
viewToModel = newViewToModel;
}
// Update the modelToView mapping
createModelToView(getModelWrapper().getRowCount());
setModelToViewFromViewToModel(true);
// And notify of change
fireRowSorterChanged(oldViewToModel);
}
private void rowsUpdated0(int firstRow, int lastRow) {
int[] oldViewToModel = getViewToModelAsInts(viewToModel);
int i, j;
int delta = lastRow - firstRow + 1;
int modelIndex;
int last;
int index;
if (getRowFilter() == null) {
// Sorting only:
// Remove the effected rows
Row[] updated = new Row[delta];
for (j = 0, i = firstRow; i <= lastRow; i++, j++) {
updated[j] = viewToModel[modelToView[i]];
}
// Sort the update rows
Arrays.sort(updated);
// Build the intermediary array: the array of
// viewToModel without the effected rows.
Row[] intermediary = new Row[viewToModel.length - delta];
for (i = 0, j = 0; i < viewToModel.length; i++) {
modelIndex = viewToModel[i].modelIndex;
if (modelIndex < firstRow || modelIndex > lastRow) {
intermediary[j++] = viewToModel[i];
}
}
// Build the new viewToModel
insertInOrder(Arrays.asList(updated), intermediary);
// Update modelToView
setModelToViewFromViewToModel(false);
} else {
// Sorting & filtering.
// Remove the effected rows, adding them to updated and setting
// modelToView to -2 for any rows that were not filtered out
List<Row> updated = new ArrayList<>(delta);
int newlyVisible = 0;
int newlyHidden = 0;
int effected = 0;
for (i = firstRow; i <= lastRow; i++) {
if (modelToView[i] == -1) {
// This row was filtered out
if (include(i)) {
// No longer filtered
updated.add(new Row(this, i));
newlyVisible++;
}
} else {
// This row was visible, make sure it should still be
// visible.
if (!include(i)) {
newlyHidden++;
} else {
updated.add(viewToModel[modelToView[i]]);
}
modelToView[i] = -2;
effected++;
}
}
// Sort the updated rows
Collections.sort(updated);
// Build the intermediary array: the array of
// viewToModel without the updated rows.
Row[] intermediary = new Row[viewToModel.length - effected];
for (i = 0, j = 0; i < viewToModel.length; i++) {
modelIndex = viewToModel[i].modelIndex;
if (modelToView[modelIndex] != -2) {
intermediary[j++] = viewToModel[i];
}
}
// Recreate viewToModel, if necessary
if (newlyVisible != newlyHidden) {
viewToModel = new Row[viewToModel.length + newlyVisible
- newlyHidden];
}
// Rebuild the new viewToModel array
insertInOrder(updated, intermediary);
// Update modelToView
setModelToViewFromViewToModel(true);
}
// And finally fire a sort event.
fireRowSorterChanged(oldViewToModel);
}
private void checkColumn(int column) {
if (column < 0 || column >= getModelWrapper().getColumnCount()) {
throw new IndexOutOfBoundsException(
"column beyond range of TableModel");
}
}
/**
* <code>DefaultRowSorterSpecialFilter.ModelWrapper</code> is responsible
* for providing the data that gets sorted by
* <code>DefaultRowSorterSpecialFilter</code>. You normally do not interact
* directly with
* <code>ModelWrapper</code>. Subclasses of
* <code>DefaultRowSorterSpecialFilter</code> provide an implementation of
* <code>ModelWrapper</code> wrapping another model. For example,
* <code>TableRowSorter</code> provides a
* <code>ModelWrapper</code> that wraps a
* <code>TableModel</code>. <p>
* <code>ModelWrapper</code> makes a distinction between values as
* <code>Object</code>s and
* <code>String</code>s. This allows implementations to provide a custom
* string converter to be used instead of invoking
* <code>toString</code> on the object.
*
* #param <M> the type of the underlying model
* #param <I> the identifier supplied to the filterInclude
* #since 1.6
* #see RowFilter
* #see RowFilter.Entry
*/
protected abstract static class ModelWrapper<M, I> {
/**
* Creates a new
* <code>ModelWrapper</code>.
*/
protected ModelWrapper() {
}
/**
* Returns the underlying model that this
* <code>Model</code> is wrapping.
*
* #return the underlying model
*/
public abstract M getModel();
/**
* Returns the number of columns in the model.
*
* #return the number of columns in the model
*/
public abstract int getColumnCount();
/**
* Returns the number of rows in the model.
*
* #return the number of rows in the model
*/
public abstract int getRowCount();
/**
* Returns the value at the specified index.
*
* #param row the row index
* #param column the column index
* #return the value at the specified index
* #throws IndexOutOfBoundsException if the indices are outside the
* range of the model
*/
public abstract Object getValueAt(int row, int column);
/**
* Returns the value as a
* <code>String</code> at the specified index. This implementation uses
* <code>toString</code> on the result from
* <code>getValueAt</code> (making sure to return an empty string for
* null values). Subclasses that override this method should never
* return null.
*
* #param row the row index
* #param column the column index
* #return the value at the specified index as a <code>String</code>
* #throws IndexOutOfBoundsException if the indices are outside the
* range of the model
*/
public String getStringValueAt(int row, int column) {
Object o = getValueAt(row, column);
if (o == null) {
return "";
}
String string = o.toString();
if (string == null) {
return "";
}
return string;
}
/**
* Returns the identifier for the specified row. The return value of
* this is used as the identifier for the
* <code>RowFilter.Entry</code> that is passed to the
* <code>RowFilter</code>.
*
* #param row the row to return the identifier for, in terms of the
* underlying model
* #return the identifier
* #see RowFilter.Entry#getIdentifier
*/
public abstract I getIdentifier(int row);
}
/**
* RowFilter.Entry implementation that delegates to the ModelWrapper.
* getFilterEntry(int) creates the single instance of this that is passed to
* the Filter. Only call getFilterEntry(int) to get the instance.
*/
private class FilterEntry extends RowFilter.Entry<M, I> {
/**
* The index into the model, set in getFilterEntry
*/
int modelIndex;
#Override
public M getModel() {
return getModelWrapper().getModel();
}
#Override
public int getValueCount() {
return getModelWrapper().getColumnCount();
}
#Override
public Object getValue(int index) {
return getModelWrapper().getValueAt(modelIndex, index);
}
#Override
public String getStringValue(int index) {
/* Original code here was:
*
* return getModelWrapper().getStringValueAt(modelIndex, index);
*/
if (accentIndiferent) {
return Normalizer.normalize((String) getModelWrapper().getStringValueAt(modelIndex, index), Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
} else {
return getModelWrapper().getStringValueAt(modelIndex, index);
}
}
#Override
public I getIdentifier() {
return getModelWrapper().getIdentifier(modelIndex);
}
}
/**
* Row is used to handle the actual sorting by way of Comparable. It will
* use the sortKeys to do the actual comparison.
*/
// NOTE: this class is static so that it can be placed in an array
private static class Row implements Comparable<Row> {
private DefaultRowSorterSpecialFilter sorter;
int modelIndex;
public Row(DefaultRowSorterSpecialFilter sorter, int index) {
this.sorter = sorter;
modelIndex = index;
}
#Override
public int compareTo(Row o) {
return sorter.compare(modelIndex, o.modelIndex);
}
}
}
And finally, a helper class, RowFilterSpecialFilter. Source (RowFilterSpecialFilter.java):
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package javax.swing;
import java.text.Normalizer;
/**
*
* #author Miroslav
*/
public abstract class RowFilterSpecialFilter {
public static <M, I> RowFilter<M, I> regexFilterAccentIndiferent(String regex) {
return RowFilter.regexFilter("(?i)" + Normalizer.normalize(regex, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));
}
public static <M, I> RowFilter<M, I> regexFilterAccentIndiferent(String regex, int... indices) {
return RowFilter.regexFilter("(?i)" + Normalizer.normalize(regex, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""), indices);
}
}
Now, how to use the filter with the accent indiferent function?
Instead of attaching a TableRowSorter<MyClassThatInheritsFromAbstractModel> to the jTable via setRowSorter method, attach a TableRowSorterSpecialFilter<MyClassThatInheritsFromAbstractModel> to it via the same method.
Don't forget to set accentIndiferent property to true. A simple myTableRowSorterSpecialFilterInstance.AccentIndiferent = true; is enough;
The code that should be fired up to apply a filter is what follows, where FilterText is the string that's going to be the filter:
RowFilter<ClientesTableModel, Object> rf;
try {
rf = RowFilterSpecialFilter.regexFilterAccentIndiferent("(?i)" + FilterText);
} catch (PatternSyntaxException ex) {
return;
}
sorter.setRowFilter(rf);
I've put so many sources to help to anyone that gets the same trouble that I had. I'm pretty sure that I've modified something else but I don't remember it, so, better to be sure and make a good contribution.
And... done!!!

Why the ClassLoader exception is raised in this situation?

I compiled successfully three files and when I tried to launch a class which contains a public static void main then I got errors. Here is the error :
C:\Documents and Settings\Ambre-28\Mes documents\JavaMESDKProjects\exempleRXTX\src\net\net>java Example
Exception in thread "main" java.lang.NoClassDefFoundError: Example (wrong name:
net/Example)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$000(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
Could not find the main class: Example. Program will exit.
Here are the codes :
package net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Vector;
/**
* This is a very simple example showing the most basic use of
* {#link net.Network} and {#link net.Network_iface}. Feel free to use,
* overwrite, or just ignore code as you like.
*
* As a default, a connection speed of 115200 baud is assumed. You can use a
* different speed by giving it as an <b>int</b> as the first command line
* argument or changing the default speed in the source code.
*
* #author Raphael Blatter (raphael#blatter.sg)
*/
public class Example implements net.Network_iface {
// set the speed of the serial port
public static int speed = 115200;
private static net.Network network;
private static boolean resend_active = false;
public static void main(String[] args) {
network = new net.Network(0, new net.Example(), 255);
// reading the speed if
if (args.length > 0) {
try {
speed = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.out.println("the speed must be an integer\n");
System.exit(1);
}
}
// initializing reader from command line
int i, inp_num = 0;
String input;
BufferedReader in_stream = new BufferedReader(new InputStreamReader(
System.in));
// getting a list of the available serial ports
Vector<String> ports = network.getPortList();
// choosing the port to connect to
System.out.println();
if (ports.size() > 0) {
System.out.println("the following serial ports have been detected:");
}
else {
System.out.println("sorry, no serial ports were found on your computer\n");
System.exit(0);
}
for (i = 0; i < ports.size(); ++i) {
System.out.println(" " + Integer.toString(i + 1) + ": " + ports.elementAt(i));
}
boolean valid_answer = false;
while (!valid_answer) {
System.out.println("enter the id (1,2,...) of the connection to connect to: ");
try {
input = in_stream.readLine();
inp_num = Integer.parseInt(input);
if ((inp_num < 1) || (inp_num >= ports.size() + 1))
System.out.println("your input is not valid");
else
valid_answer = true;
} catch (NumberFormatException ex) {
System.out.println("please enter a correct number");
} catch (IOException e) {
System.out.println("there was an input error\n");
System.exit(1);
}
}
// connecting to the selected port
if (network.connect(ports.elementAt(inp_num - 1), speed)) {
System.out.println();
} else {
System.out.println("sorry, there was an error connecting\n");
System.exit(1);
}
// asking whether user wants to mirror traffic
System.out.println("do you want this tool to send back all the received messages?");
valid_answer = false;
while (!valid_answer) {
System.out.println("'y' for yes or 'n' for no: ");
try {
input = in_stream.readLine();
if (input.equals("y")) {
resend_active = true;
valid_answer = true;
} else if (input.equals("n")) {
valid_answer = true;
} else if (input.equals("q")) {
System.out.println("example terminated\n");
System.exit(0);
}
} catch (IOException e) {
System.out.println("there was an input error\n");
System.exit(1);
}
}
// reading in numbers (bytes) to be sent over the serial port
System.out.println("type 'q' to end the example");
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
}
System.out.println("\nenter a number between 0 and 254 to be sent ('q' to exit): ");
try {
input = in_stream.readLine();
if (input.equals("q")) {
System.out.println("example terminated\n");
network.disconnect();
System.exit(0);
}
inp_num = Integer.parseInt(input);
if ((inp_num > 255) || (inp_num < 0)) {
System.out.println("the number you entered is not valid");
} else {
int temp[] = { inp_num };
network.writeSerial(1, temp); // ecriture dans le port série
System.out.println("sent " + inp_num + " over the serial port");
}
} catch (NumberFormatException ex) {
System.out.println("please enter a correct number");
} catch (IOException e) {
System.out.println("there was an input error");
}
}
}
/**
* Implementing {#link net.Network_iface#networkDisconnected(int)}, which is
* called when the connection has been closed. In this example, the program
* is ended.
*
* #see net.Network_iface
*/
public void networkDisconnected(int id) {
System.exit(0);
}
/**
* Implementing {#link net.Network_iface#parseInput(int, int, int[])} to
* handle messages received over the serial port. In this example, the
* received bytes are written to command line (0 to 254) and the message is
* sent back over the same serial port.
*
* #see net.Network_iface
*/
public void parseInput(int id, int numBytes, int[] message) {
if (resend_active) {
network.writeSerial(numBytes, message);
System.out.print("received and sent back the following message: ");
} else {
System.out.print("received the following message: ");
}
System.out.print(message[0]);
for (int i = 1; i < numBytes; ++i) {
System.out.print(", ");
System.out.print(message[i]);
}
System.out.println();
}
/**
* Implementing {#link net.Network_iface#writeLog(int, String)}, which is
* used to write information concerning the connection. In this example, all
* the information is simply written out to command line.
*
* #see net.Network_iface
*/
public void writeLog(int id, String text) {
System.out.println(" log: |" + text + "|");
}
}
package net;
import gnu.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Vector;
/**
* Used to simplify communication over a Serial port. Using the RXTX-library
* (rxtx.qbang.org), one connection per instance of this class can be handled.
* In addition to handling a connection, information about the available Serial
* ports can be received using this class.
*
* A separate {#link Thread} is started to handle messages that are being
* received over the Serial interface.
*
* This class also makes packages out of a stream of bytes received, using a
* {#link #divider}, and sending these packages as an array of <b>int</b>s (each
* between 0 and 255) to a function implemented by a class implementing the
* {#link net.Network_iface}-interface.
*
* #author Raphael Blatter (raphael#blatter.sg)
* #author heavily using code examples from the RXTX-website (rxtx.qbang.org)
*/
public class Network {
private InputStream inputStream;
private OutputStream outputStream;
/**
* The status of the connection.
*/
private boolean connected = false;
/**
* The Thread used to receive the data from the Serial interface.
*/
private Thread reader;
private SerialPort serialPort;
/**
* Communicating between threads, showing the {#link #reader} when the
* connection has been closed, so it can {#link Thread#join()}.
*/
private boolean end = false;
/**
* Link to the instance of the class implementing {#link net.Network_iface}.
*/
private Network_iface contact;
/**
* A small <b>int</b> representing the number to be used to distinguish
* between two consecutive packages. It can only take a value between 0 and
* 255. Note that data is only sent to
* {#link net.Network_iface#parseInput(int, int, int[])} once the following
* 'divider' could be identified.
*
* As a default, <b>255</b> is used as a divider (unless specified otherwise
* in the constructor).
*
* #see net.Network#Network(int, Network_iface, int)
*/
private int divider;
/**
* <b>int</b> identifying the specific instance of the Network-class. While
* having only a single instance, 'id' is irrelevant. However, having more
* than one open connection (using more than one instance of {#link Network}
* ), 'id' helps identifying which Serial connection a message or a log
* entry came from.
*/
private int id;
private int[] tempBytes;
int numTempBytes = 0, numTotBytes = 0;
/**
* #param id
* <b>int</b> identifying the specific instance of the
* Network-class. While having only a single instance,
* {#link #id} is irrelevant. However, having more than one open
* connection (using more than one instance of Network),
* {#link #id} helps identifying which Serial connection a
* message or a log entry came from.
*
* #param contact
* Link to the instance of the class implementing
* {#link net.Network_iface}.
*
* #param divider
* A small <b>int</b> representing the number to be used to
* distinguish between two consecutive packages. It can take a
* value between 0 and 255. Note that data is only sent to
* {#link net.Network_iface#parseInput(int, int, int[])} once the
* following {#link #divider} could be identified.
*/
public Network(int id, Network_iface contact, int divider) {
this.contact = contact;
this.divider = divider;
if (this.divider > 255)
this.divider = 255;
if (this.divider < 0)
this.divider = 0;
this.id = id;
tempBytes = new int[1024];
}
/**
* Just as {#link #Network(int, Network_iface, int)}, but with a default
* {#link #divider} of <b>255</b>.
*
* #see #Network(int, Network_iface, int)
*/
public Network(int id, Network_iface contact) {
this(id, contact, 255);
}
/**
* Just as {#link #Network(int, Network_iface, int)}, but with a default
* {#link #divider} of <b>255</b> and a default {#link #id} of 0. This
* constructor may mainly be used if only one Serial connection is needed at
* any time.
*
* #see #Network(int, Network_iface, int)
*/
public Network(Network_iface contact) {
this(0, contact);
}
/**
* This method is used to get a list of all the available Serial ports
* (note: only Serial ports are considered). Any one of the elements
* contained in the returned {#link Vector} can be used as a parameter in
* {#link #connect(String)} or {#link #connect(String, int)} to open a
* Serial connection.
*
* #return A {#link Vector} containing {#link String}s showing all available
* Serial ports.
*/
#SuppressWarnings("unchecked")
public Vector<String> getPortList() {
Enumeration<CommPortIdentifier> portList;
Vector<String> portVect = new Vector<String>();
portList = CommPortIdentifier.getPortIdentifiers();
CommPortIdentifier portId;
while (portList.hasMoreElements()) {
portId = (CommPortIdentifier) portList.nextElement();
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
portVect.add(portId.getName());
}
}
contact.writeLog(id, "found the following ports:");
for (int i = 0; i < portVect.size(); i++) {
contact.writeLog(id, (" " + (String) portVect.elementAt(i)));
}
return portVect;
}
/**
* Just as {#link #connect(String, int)}, but using 115200 bps as a default
* speed of the connection.
*
* #param portName
* The name of the port the connection should be opened to (see
* {#link #getPortList()}).
* #return <b>true</b> if the connection has been opened successfully,
* <b>false</b> otherwise.
* #see #connect(String, int)
*/
public boolean connect(String portName) {
return connect(portName, 115200);
}
/**
* Opening a connection to the specified Serial port, using the specified
* speed. After opening the port, messages can be sent using
* {#link #writeSerial(String)} and received data will be packed into
* packets (see {#link #divider}) and forwarded using
* {#link net.Network_iface#parseInput(int, int, int[])}.
*
* #param portName
* The name of the port the connection should be opened to (see
* {#link #getPortList()}).
* #param speed
* The desired speed of the connection in bps.
* #return <b>true</b> if the connection has been opened successfully,
* <b>false</b> otherwise.
*/
public boolean connect(String portName, int speed) {
CommPortIdentifier portIdentifier;
boolean conn = false;
try {
portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
if (portIdentifier.isCurrentlyOwned()) {
contact.writeLog(id, "Error: Port is currently in use");
} else {
serialPort = (SerialPort) portIdentifier.open("RTBug_network",
2000);
serialPort.setSerialPortParams(speed, SerialPort.DATABITS_8,
SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
inputStream = serialPort.getInputStream();
outputStream = serialPort.getOutputStream();
reader = (new Thread(new SerialReader(inputStream)));
end = false;
reader.start();
connected = true;
contact.writeLog(id, "connection on " + portName
+ " established");
conn = true;
}
} catch (NoSuchPortException e) {
contact.writeLog(id, "the connection could not be made");
e.printStackTrace();
} catch (PortInUseException e) {
contact.writeLog(id, "the connection could not be made");
e.printStackTrace();
} catch (UnsupportedCommOperationException e) {
contact.writeLog(id, "the connection could not be made");
e.printStackTrace();
} catch (IOException e) {
contact.writeLog(id, "the connection could not be made");
e.printStackTrace();
}
return conn;
}
/**
* A separate class to use as the {#link net.Network#reader}. It is run as a
* separate {#link Thread} and manages the incoming data, packaging them
* using {#link net.Network#divider} into arrays of <b>int</b>s and
* forwarding them using
* {#link net.Network_iface#parseInput(int, int, int[])}.
*
*/
private class SerialReader implements Runnable {
InputStream in;
public SerialReader(InputStream in) {
this.in = in;
}
public void run() {
byte[] buffer = new byte[1024];
int len = -1, i, temp;
try {
while (!end) {
if ((in.available()) > 0) {
if ((len = this.in.read(buffer)) > -1) {
for (i = 0; i < len; i++) {
temp = buffer[i];
// adjust from C-Byte to Java-Byte
if (temp < 0)
temp += 256;
if (temp == divider) {
if (numTempBytes > 0) {
contact.parseInput(id, numTempBytes,
tempBytes);
}
numTempBytes = 0;
} else {
tempBytes[numTempBytes] = temp;
++numTempBytes;
}
}
}
}
}
} catch (IOException e) {
end = true;
try {
outputStream.close();
inputStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
serialPort.close();
connected = false;
contact.networkDisconnected(id);
contact.writeLog(id, "connection has been interrupted");
}
}
}
/**
* Simple function closing the connection held by this instance of
* {#link net.Network}. It also ends the Thread {#link net.Network#reader}.
*
* #return <b>true</b> if the connection could be closed, <b>false</b>
* otherwise.
*/
public boolean disconnect() {
boolean disconn = true;
end = true;
try {
reader.join();
} catch (InterruptedException e1) {
e1.printStackTrace();
disconn = false;
}
try {
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
disconn = false;
}
serialPort.close();
connected = false;
contact.networkDisconnected(id);
contact.writeLog(id, "connection disconnected");
return disconn;
}
/**
* #return Whether this instance of {#link net.Network} has currently an
* open connection of not.
*/
public boolean isConnected() {
return connected;
}
/**
* This method is included as a legacy. Depending on the other side of the
* Serial port, it might be easier to send using a String. Note: this method
* does not add the {#link #divider} to the end.
*
* If a connection is open, a {#link String} can be sent over the Serial
* port using this function. If no connection is available, <b>false</b> is
* returned and a message is sent using
* {#link net.Network_iface#writeLog(int, String)}.
*
* #param message
* The {#link String} to be sent over the Serial connection.
* #return <b>true</b> if the message could be sent, <b>false</b> otherwise.
*/
public boolean writeSerial(String message) {
boolean success = false;
if (isConnected()) {
try {
outputStream.write(message.getBytes());
success = true;
} catch (IOException e) {
disconnect();
}
} else {
contact.writeLog(id, "No port is connected.");
}
return success;
}
/**
* If a connection is open, an <b>int</b> between 0 and 255 (except the
* {#link net.Network#divider}) can be sent over the Serial port using this
* function. The message will be finished by sending the
* {#link net.Network#divider}. If no connection is available, <b>false</b>
* is returned and a message is sent using
* {#link net.Network_iface#writeLog(int, String)}.
*
* #param numBytes
* The number of bytes to send over the Serial port.
* #param message
* [] The array of<b>int</b>s to be sent over the Serial
* connection (between 0 and 256).
* #return <b>true</b> if the message could be sent, <b>false</b> otherwise
* or if one of the numbers is equal to the #{#link Network#divider}
* .
*/
public boolean writeSerial(int numBytes, int message[]) {
boolean success = true;
int i;
for (i = 0; i < numBytes; ++i) {
if (message[i] == divider) {
success = false;
break;
}
}
if (success && isConnected()) {
try {
for (i = 0; i < numBytes; ++i) {
outputStream.write(changeToByte(message[i]));
}
outputStream.write(changeToByte(divider));
} catch (IOException e) {
success = false;
disconnect();
}
} else if (!success) {
// message contains the divider
contact.writeLog(id, "The message contains the divider.");
} else {
contact.writeLog(id, "No port is connected.");
}
return success;
}
private byte changeToByte(int num) {
byte number;
int temp;
temp = num;
if (temp > 255)
temp = 255;
if (temp < 0)
temp = 0;
number = (byte) temp;
return number;
}
}
package net;
/**
* An instance of a class implementing this interface has to be passed to the
* constructor of {#link net.Network}. It will be used by {#link net.Network} to
* forward received messages, write to a log and take action when the connection
* is closed.
*
* #see net.Network#Network(int, Network_iface, int)
*
* #author Raphael Blatter (raphael#blatter.sg)
*/
public interface Network_iface {
/**
* Is called to write connection information to the log. The information can
* either be ignored, directed to stdout or written out to a specialized
* field or file in the program.
*
* #param id
* The <b>int</b> passed to
* {#link net.Network#Network(int, Network_iface, int)} in the
* constructor. It can be used to identify which instance (which
* connection) a message comes from, when several instances of
* {#link net.Network} are connected to the same instance of a
* class implementing this interface.
* #param text
* The text to be written into the log in human readable form.
* Corresponds to information about the connection or ports.
*/
public void writeLog(int id, String text);
/**
* Is called when sequence of bytes are received over the Serial interface.
* It sends the bytes (as <b>int</b>s between 0 and 255) between the two
* {#link net.Network#divider}s passed via the constructor of
* {#link net.Network} (
* {#link net.Network#Network(int, Network_iface, int)}), without the
* {#link net.Network#divider}s. Messages are only forwarded using this
* function, once a {#link net.Network#divider} has been recognized in the
* incoming stream.
*
* #param id
* The <b>int</b> passed to
* {#link net.Network#Network(int, Network_iface, int)} in the
* constructor. It can be used to identify which instance a
* message comes from, when several instances of
* {#link net.Network} are connected to the same instance of a
* class implementing this interface.
* #param numBytes
* Number of valid bytes contained in the message
* #param message
* Message received over the Serial interface. The complete array
* of bytes (as <b>int</b>s between 0 and 255) between
* {#link net.Network#divider} is sent (without
* {#link net.Network#divider}s).
*/
public void parseInput(int id, int numBytes, int[] message);
/**
* Is called when the network has been disconnected. This call can e.g. be
* used to show the connection status in a GUI or inform the user using
* other means.
*
* #param id
* {#link net.Network#id} of the corresponding
* {#link net.Network} instance (see {#link net.Network#id}).
*/
public void networkDisconnected(int id);
}
Each class is a separate file. So why this error is raised ?
You should be launching it from the parent directory as
java net.Example
When you run the java command, it takes the full class name, including the package name. So in your case the JVM was trying to find a class just called Example - it found Example.class as a file, but then failed because that class file contains the class net.Example, not Example.
You need to be in the parent directory (...\exempleRXTX\src\net) so that when the JVM looks for net.Example it will look in the ...\exempleRXTX\src\net\net directory for a file called Example.class.
Check where the .class files are being generated and ensure that classpath settings are right, However it seems that ur nt running the `java command' from a proper directory as mentioned by #Jon.
Note: Specifying '.'(single dot) as the classpath value will configure java to search for the .class files in same directory (from where u r trying java command).

Sequential Guid in Java

Considering the post I've made about the sequential guid performance on Microsoft.NET framework (see What are the performance improvement of Sequential Guid over standard Guid?) does somebody have a proper, sure, fast and well working Java implementation of the same algorithm implemented in the Windows DLLs?
Regards
Massimo
See this article: http://www.informit.com/articles/article.aspx?p=25862&seqNum=7 (linked to Page 7).
It contains an algorithm for what the author refers to as "COMB" Guids; I reproduce his code (SQL) below:
SET #aGuid = CAST(CAST(NEWID() AS BINARY(10))
+ CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER)
Trivial to convert this to Java, or your desired language. The obvious underlying principle is to make the date a component of the Guid. The entire article is a good read, as he does a nice analysis of the performance of the various approaches.
For sequential UUIDs, you are looking for a version 1 UUID. Java UUID Generator project seems to work quite well and is pretty easy to use:
Generators.timeBasedGenerator().generate().toString()
This page links to a couple of version 1 (sequential) UUID implementations in Java:
http://johannburkard.de/blog/programming/java/Java-UUID-generators-compared.html
This utility class that generates COMB UUIDs, conceived by Jimmy Nilsson in this article: http://www.informit.com/articles/article.aspx?p=25862. Feel free to use and share.
package your.package.name;
import java.security.SecureRandom;
import java.util.Random;
import java.util.UUID;
/**
* Utility class that creates COMB UUIDs.
*
* The COMB UUIDs combine the creation time and random bytes.
*
* The PREFIX or SUFFIX has 6 bytes and corresponds to the milliseconds since
* 1970-01-01T00:00:00Z (Unix epoch).
*
* For RFC-4122 compliance, it uses the version number 4.
*
* Read: The Cost of GUIDs as Primary Keys
* http://www.informit.com/articles/article.aspx?p=25862
*
*/
public abstract class CombUuidCreator {
private static final int RANDOM_VERSION = 4;
/**
* Returns a prefix COMB UUID.
*
* It uses a thread local {#link SecureRandom}.
*
* #return a random-based UUID
*/
public static UUID getPrefixComb() {
return getPrefixComb(SecureRandomLazyHolder.THREAD_LOCAL_RANDOM.get());
}
/**
* Returns a prefix COMB UUID.
*
* It uses any instance of {#link Random}.
*
* #return a random-based UUID
*/
public static UUID getPrefixComb(Random random) {
return getCombGuid(random, /* prefix = */true);
}
/**
* Returns a suffix COMB UUID.
*
* It uses a thread local {#link SecureRandom}.
*
* #return a random-based UUID
*/
public static UUID getSuffixComb() {
return getSuffixComb(SecureRandomLazyHolder.THREAD_LOCAL_RANDOM.get());
}
/**
* Returns a suffix COMB UUID.
*
* It uses any instance of {#link Random}.
*
* #return a random-based UUID
*/
public static UUID getSuffixComb(Random random) {
return getCombGuid(random, /* prefix = */false);
}
/**
* Returns prefix or suffix COMB UUID.
*
* It uses any instance of {#link Random}.
*
* #return a random-based UUID
*/
private static UUID getCombGuid(Random random, boolean prefix) {
long msb = 0;
long lsb = 0;
// (3) set bits randomly
final byte[] bytes = new byte[16];
random.nextBytes(bytes);
final long rand0 = (bytes[8] << 8) | (bytes[9] & 0xff);
final long rand1 = toNumber(bytes, 0, 8);
// Insert the prefix in the MSB
final long timestamp = System.currentTimeMillis();
if (prefix) {
msb = (rand0 & 0x000000000000ffffL) | ((timestamp & 0x0000ffffffffffffL) << 16);
lsb = rand1;
} else {
msb = rand1;
lsb = (rand0 << 48) | (timestamp & 0x0000ffffffffffffL);
}
// Apply version and variant bits (required for RFC-4122 compliance)
msb = (msb & 0xffffffffffff0fffL) | (RANDOM_VERSION & 0x0f) << 12; // apply version bits
lsb = (lsb & 0x3fffffffffffffffL) | 0x8000000000000000L; // apply variant bits
// Return the UUID
return new UUID(msb, lsb);
}
private static long toNumber(final byte[] bytes, final int start, final int length) {
long result = 0;
for (int i = start; i < length; i++) {
result = (result << 8) | (bytes[i] & 0xff);
}
return result;
}
// Holds thread local secure random
private static class SecureRandomLazyHolder {
static final ThreadLocal<Random> THREAD_LOCAL_RANDOM = ThreadLocal.withInitial(SecureRandom::new);
}
/**
* For tests!
*/
public static void main(String[] args) {
Random random = new Random();
System.out.println("// Prefix COMB using thread local `java.security.SecureRandom` (DEFAULT)");
System.out.println("CombUuidCreator.getPrefixComb()");
System.out.println();
for (int i = 0; i < 5; i++) {
System.out.println(" " + CombUuidCreator.getPrefixComb());
}
System.out.println("|----prefix---|----------------------|");
System.out.println();
System.out.println("// Prefix COMB using `java.util.Random` (FASTER)");
System.out.println("CombUuidCreator.getPrefixComb(new Random())");
System.out.println();
for (int i = 0; i < 5; i++) {
System.out.println(" " + CombUuidCreator.getPrefixComb(random));
}
System.out.println("|----prefix---|----------------------|");
System.out.println();
System.out.println("// Suffix COMB using thread local `java.security.SecureRandom` (DEFAULT)");
System.out.println("CombUuidCreator.getSuffixComb()");
System.out.println();
for (int i = 0; i < 5; i++) {
System.out.println(" " + CombUuidCreator.getSuffixComb());
}
System.out.println("|-----------------------|---suffix---|");
System.out.println();
System.out.println("// Suffix COMB using `java.util.Random` (FASTER)");
System.out.println("CombUuidCreator.getSuffixComb(new Random())");
System.out.println();
for (int i = 0; i < 5; i++) {
System.out.println(" " + CombUuidCreator.getSuffixComb(random));
}
System.out.println("|-----------------------|---suffix---|");
}
}
This is the output:
// Prefix COMB using thread local `java.security.SecureRandom` (DEFAULT)
CombUuidCreator.getPrefixComb()
0173861f-4445-459b-87d2-39a970520fff
0173861f-4445-465d-a216-7b13d86c83a1
0173861f-4445-4c67-b75e-3845c2911420
|----prefix---|----------------------|
// Prefix COMB using `java.util.Random` (FASTER)
CombUuidCreator.getPrefixComb(new Random())
0173861f-4445-44f6-bfa4-e272c9c369aa
0173861f-4445-446e-baf2-6db6ab808094
0173861f-4445-40e8-a452-184dcf9736fd
|----prefix---|----------------------|
// Suffix COMB using thread local `java.security.SecureRandom` (DEFAULT)
CombUuidCreator.getSuffixComb()
726b6717-001a-4317-9a9b-0173861f4446
dfdce2d2-7517-4a3f-9f3d-0173861f4446
a7fd6236-8065-4395-b49a-0173861f4446
|-----------------------|---suffix---|
// Suffix COMB using `java.util.Random` (FASTER)
CombUuidCreator.getSuffixComb(new Random())
41a6a4cd-eb4c-410f-8eb2-0173861f4446
7c0a315e-54de-476a-a2a8-0173861f4446
4e9ddf9e-ac07-4cf3-bf3f-0173861f4446
|-----------------------|---suffix---|
You can also use the uuid-creator library. See these examples:
// Create a prefix COMB UUID
UUID uuid = UuidCreator.getPrefixComb();
// Create a suffix COMB UUID
UUID uuid = UuidCreator.getSuffixComb();
Project page: https://github.com/f4b6a3/uuid-creator
I use this to generate UUIDs (Universally Unique IDs) for my DTOs which act as surrogate keys for transient collections. Don't know if it's the same thing, but it may point you in the right direction.
import java.util.UUID;
...
private String uuid=null;
...
protected String getUuid() {
synchronized (this) {
if (null ==uuid) {
uuid = UUID.randomUUID().toString();
}
return uuid;
}
}

Categories

Resources