This question already has answers here:
Check if a given time lies between two times regardless of date
(32 answers)
Closed 3 years ago.
I'm trying to determine if the current time e.g. 19:30:00 is between 19:00:00 & 03:00:00 next day, but my code is failing.
my code fails with this condition
can i use date to campare time if yes please let me know how
import java.util.Calendar;
import java.text.SimpleDateFormat;
public class DateUtils {
// format 24hre ex. 12:12 , 17:15
private static String HOUR_FORMAT = "HH:mm";
private DateUtils() { }
public static String getCurrentHour() {
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdfHour = new SimpleDateFormat(HOUR_FORMAT);
String hour = sdfHour.format(cal.getTime());
return hour;
}
/**
* #param target hour to check
* #param start interval start
* #param end interval end
* #return true true if the given hour is between
*/
public static boolean isHourInInterval(String target, String start, String end) {
return ((target.compareTo(start) >= 0)
&& (target.compareTo(end) <= 0));
}
/**
* #param start interval start
* #param end interval end
* #return true true if the current hour is between
*/
public static boolean isNowInInterval(String start, String end) {
return DateUtils.isHourInInterval
(DateUtils.getCurrentHour(), start, end);
}
// TEST
public static void main (String[] args) {
String now = DateUtils.getCurrentHour();
String start = "14:00";
String end = "14:26";
System. out.println(now + " between " + start + "-" + end + "?");
System. out.println(DateUtils.isHourInInterval(now,start,end));
/*
* output example :
* 21:01 between 14:00-14:26?
* false
*
*/
}
}
java.time.LocalTime is your friend here. Below is a quick example, sure it can be done somewhat shorter.
void test(){
var tz = ZoneId.of("CET");
var anyDate = LocalDate.of(2019,12,4);
var x = ZonedDateTime.of(LocalDateTime.of(anyDate, LocalTime.of(18,59)),tz).toInstant();
System.out.println(testTime(Clock.fixed( ZonedDateTime.of(LocalDateTime.of(anyDate, LocalTime.of(18,59)),tz).toInstant(),tz)));
System.out.println(testTime(Clock.fixed( ZonedDateTime.of(LocalDateTime.of(anyDate, LocalTime.of(19,01)),tz).toInstant(),tz)));
System.out.println(testTime(Clock.fixed( ZonedDateTime.of(LocalDateTime.of(anyDate, LocalTime.of(00,00)),tz).toInstant(),tz)));
System.out.println(testTime(Clock.fixed( ZonedDateTime.of(LocalDateTime.of(anyDate, LocalTime.of(02,59)),tz).toInstant(),tz)));
System.out.println(testTime(Clock.fixed( ZonedDateTime.of(LocalDateTime.of(anyDate, LocalTime.of(03,01)),tz).toInstant(),tz)));
}
boolean testTime(Clock clock){
var evening =LocalTime.of(19,00);
var midnight =LocalTime.of(00,00);
var night =LocalTime.of(03,00);
LocalTime wallTime = LocalTime.now(clock);
return (wallTime.isAfter(evening) && wallTime.isBefore(midnight.minusNanos(1))) || (midnight.isBefore(wallTime) && wallTime.isBefore(night)) || wallTime.equals(midnight);
}
For some reason, I can't seem to fix this giving me a NullPointerException. I print out poly.term.degree without any errors, and then set poly.next equal to poly and then get a nullPointerException when I try to print out poly.next.term.degree which should seemingly be the same. I'm aware this is incomplete code but I think this is my main issue.
public Polynomial add(Polynomial p)
{
Polynomial newPoly = new Polynomial();
newPoly.poly = new Node(0,0,null);
Polynomial myCurr = this;
Polynomial otherCurr = p;
while(myCurr.poly != null)
{
int myDeg = myCurr.poly.term.degree;
int otherDeg = p.poly.term.degree;
float myCo = myCurr.poly.term.coeff;
float otherCo = otherCurr.poly.term.coeff;
if(myDeg == otherDeg)
{
System.out.println("degrees "+myDeg + " and "+ otherDeg+ " are equal, creating new node...");
Node n = new Node(myCo+otherCo,p.poly.term.degree, newPoly.poly.next);
System.out.println(newPoly.poly.term.degree);
newPoly.poly.next = newPoly.poly;
newPoly.poly = n;
System.out.println(newPoly.poly.next.term.degree); // Gives me a NullPointerException
}
Also, the constructors and everything for these classes is below.
package poly;
import java.io.*;
import java.util.StringTokenizer;
/**
* This class implements a term of a polynomial.
*
* #author runb-cs112
*
*/
class Term {
/**
* Coefficient of term.
*/
public float coeff;
/**
* Degree of term.
*/
public int degree;
/**
* Initializes an instance with given coefficient and degree.
*
* #param coeff Coefficient
* #param degree Degree
*/
public Term(float coeff, int degree) {
this.coeff = coeff;
this.degree = degree;
}
/* (non-Javadoc)
* #see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object other) {
return other != null &&
other instanceof Term &&
coeff == ((Term)other).coeff &&
degree == ((Term)other).degree;
}
/* (non-Javadoc)
* #see java.lang.Object#toString()
*/
public String toString() {
if (degree == 0) {
return coeff + "";
} else if (degree == 1) {
return coeff + "x";
} else {
return coeff + "x^" + degree;
}
}
}
/**
* This class implements a linked list node that contains a Term instance.
*
* #author runb-cs112
*
*/
class Node {
/**
* Term instance.
*/
Term term;
/**
* Next node in linked list.
*/
Node next;
/**
* Initializes this node with a term with given coefficient and degree,
* pointing to the given next node.
*
* #param coeff Coefficient of term
* #param degree Degree of term
* #param next Next node
*/
public Node(float coeff, int degree, Node next) {
term = new Term(coeff, degree);
this.next = next;
}
}
/**
* This class implements a polynomial.
*
* #author runb-cs112
*
*/
public class Polynomial {
/**
* Pointer to the front of the linked list that stores the polynomial.
*/
Node poly;
/**
* Initializes this polynomial to empty, i.e. there are no terms.
*
*/
public Polynomial() {
poly = null;
}
/**
* Reads a polynomial from an input stream (file or keyboard). The storage format
* of the polynomial is:
* <pre>
* <coeff> <degree>
* <coeff> <degree>
* ...
* <coeff> <degree>
* </pre>
* with the guarantee that degrees will be in descending order. For example:
* <pre>
* 4 5
* -2 3
* 2 1
* 3 0
* </pre>
* which represents the polynomial:
* <pre>
* 4*x^5 - 2*x^3 + 2*x + 3
* </pre>
*
* #param br BufferedReader from which a polynomial is to be read
* #throws IOException If there is any input error in reading the polynomial
*/
public Polynomial(BufferedReader br) throws IOException {
String line;
StringTokenizer tokenizer;
float coeff;
int degree;
poly = null;
while ((line = br.readLine()) != null) {
tokenizer = new StringTokenizer(line);
coeff = Float.parseFloat(tokenizer.nextToken());
degree = Integer.parseInt(tokenizer.nextToken());
poly = new Node(coeff, degree, poly);
}
}
I think the problem lies here:
newPoly.poly.next = newPoly.poly;
newPoly.poly = n;
At first you say, that newPoly.poly.next = newPoly.poly; so you assign the current element to the next, which is recursive. And then you say newPoly.poly = n; . So you assign a new element to newPoly. I think that the garbage collector deletes the newPoly element, because it is overwritten, so you lose the reference to the newPoly element. Which means when you access it later you get a nullpointer exception. You could fix this like this:
newPoly.poly.next = n;
//and dont forget to set the next pointer of the new elemnt to 0
n.next = NULL;
Just assign the new element to the next element.
EDIT
#hendersawn
You could sort the list. See below:
sort(Node head_p){ //do not change the head, or you will lose the beginning.
Node tmp_p;
Node curr_p = head_p;
while(curr_p != NULL){
if(curr_p.poly.term.degree < curr_p.next.poly.term.degree) //either degree is smaller or greater
{//swap
tmp_p = curr_p; //save first element
curr_p = curr_p.next; //set first element to second
//now the 2 element is the actual third element so we need
//to put the first between the second and the third
tmp_p.next = curr_p.next; //set next of first to third
curr_p.next = tmp_p; //set second element to the first that we saved before
}
curr_p = curr_p.next; //move to next element...rinse repeat
}
}
newPoly might be null
newPoly.poly might be null
newPoly.poly.next might be null
newPoly.poly.next.term might be null
or
newPoly.poly.next.term.degree might be null.
To avoid NullPointerException, you need to make sure that any member used is initialized with a proper value.
Nothing is obviously null that I can tell, however with four 'dots' (something.something.something.something) of Object Oriented Indirection, you're going to encounter this problem a lot. Usually two - three 'dots' on a single line are all you should ever do, but since that's more about the design and not the error, I digress.
The way to find this problem would be to either:
Put a breakpoint right at that line and see what the variables are there, and which one is null or
Do a System.out.println for each of the components to see which one breaks it, and work backwards from there. ex:
System.out.println(newPoly.poly.next.term.degree); // Gives me a NullPointerException
System.out.println(newPoly);
System.out.println(newPoly.poly);
System.out.println(newPoly.poly.next);
System.out.println(newPoly.poly.next.term);
because the nullPointerException would only get thrown on one of those (it can't be degree, otherwise that statement would have just printed 'null'
if I had to bet, I'd say it's probably newPoly.poly.next which is null
the lines:
newPoly.poly.next = newPoly.poly;
newPoly.poly = n;
Superficially seem like they would be the culprit of your trouble, since you're assigning the 'next' of your newPoly.poly, but then you re-assign your newPoly.poly, and lose that old reference to .next, I think.
Good luck! hope that helps.
**New edit info at bottom**
I have created a Tech support program that simulates an electronic tech support system. It is supposed to have a client request support through a ticket and assign it to the appropriate agent. It has four classes (client, agent, ticket, and the actual tech support system).
Ticket class:
/**
* Ticket.java
*
* This class is part of Programming
* Assignment 6 - Tech Support System
* for CS1440 Summer 2014.
*
* #author Brandon C. Eason
* #author
* #version
*/
/**
* This class represents a support ticket
* in the tech support ticket.
*/
public class Ticket
{
//the number of minutes for resolving a premium ticket
private static final int PREMIUM = 30;
//the number of minutes for resolving a basic ticket
private static final int BASIC = 60;
//the number of tickets generated
private static int ticketNumber;
//the ticket id, built from the current date and ticket number
private String id;
//the problem description
private String description;
//the number of minutes since the ticket was entered
private int minutes;
//the client that requested this ticket
private Client requester;
//the agent assigned to this ticket
private Agent solver;
/**
* Fully parameterized constructor for Ticket.
*
* #param date - the date this ticket was created
* #param description - this ticket's problem description
* #param minutes - the number of minutes since this ticket was entered
* #param requester - the client that requested this ticket
*/
public Ticket(String date, String description, int minutes,
Client requester)
{
ticketNumber++;
id = date + "-" + ticketNumber;
this.description = description;
this.minutes = minutes;
this.requester = new Client(requester);
}
/**
* Accessor for ticket number.
*
* #return the number of tickets generated
*/
public static int getNumTickets()
{
return ticketNumber;
}
/**
* Accessor for id.
*
* #return this ticket's id
*/
public String getID()
{
return id;
}
/**
* Accessor for description.
*
* #return this ticket's description
*/
public String getDescription()
{
return description;
}
/**
* Accessor for minutes.
*
* #return the number of minutes since this ticket was entered
*/
public int getMinutes()
{
return minutes;
}
/**
* Accessor for the Client requesting the ticket.
*
* #return a copy of the Client requesting this ticket
*/
public Client getClient()
{
return requester;
}
/**
* Assign this ticket to an agent. The method makes a copy of the
* Agent parameter and sets the solver field to that copy.
*
* #param solver - the agent this ticket is assigned to
*/
public void setAgent(Agent solver)
{
String name = solver.getName();
String id = solver.getID();
String specialty = solver.getSpecialty();
int time = solver.getTime();
solver = new Agent(name, id, specialty, time);
}
/**
* Determine the minutes this ticket is overdue.
* Basic service level clients should have their
* tickets resolved within 60 minutes. Premium
* service level clients should have their tickets
* resolved within 30 minutes.
*
* #return the number of minutes the ticket is overdue
*/
public int timeOverdue()
{
int timeOverdue;
if(requester.hasPremium())
{
timeOverdue = (PREMIUM - this.minutes);
}
else
{
timeOverdue = (BASIC - this.minutes);
}
return timeOverdue;
}
/**
* Return a string predicting the resolution time of the ticket.
*
* #return a prediction of the resolution time
*/
public String prediction()
{
String predict = "Resolution Time: ";
int minutes = timeOverdue() + solver.getTime();
if (minutes > 0)
{
predict += minutes + " minutes behind schedule";
}
else if (minutes < 0)
{
predict += Math.abs(minutes) + " minutes ahead of schedule";
}
else
{
predict += "On time";
}
return predict;
}
/**
* Determine whether this ticket is overdue.
*
* #return true if this ticket is overdue
*/
public boolean isOverdue()
{
return timeOverdue() > 0;
}
/**
* Build a string representation of this ticket.
*
* #return this ticket's data
*/
public String toString()
{
//begin with id and description
String data = "ID#: " + getID();
data += "\r\nProblem: " + getDescription();
//add time overdue, if any
if (isOverdue())
{
data += "\r\nOverdue: " + timeOverdue() + " minutes";
}
//add client data
data += "\r\n------------";
data += "\r\nRequested by\r\n";
data += "------------\r\n";
data += requester;
//add agent data
data += "\r\n-----------";
data += "\r\nAssigned to\r\n";
data += "-----------\r\n";
data += solver;
//add projected resolution time
data += "\r\n" + prediction();
return data;
}
/**
* Determine if this ticket is a duplicate of another.
* Tickets are duplicates if they have the same description
* and Client.
*
* #param other - the other Ticket
* #return true if this ticket is a duplicate of the other
*/
public boolean equals(Ticket other)
{
if(description.equals(other.description) && requester.equals(other.requester))
{
return true;
}
else
{
return false;
}
}
}
Agent class:
/**
* Agent.java
*
* This class is part of Programming
* Assignment 6 - Tech Support System
* for CS1440 Summer 2014.
*
* #author Brandon C. Eason
* #author
* #version
*/
/**
* This clas represents an agent profile
* in the tech support system.
*/
public class Agent
{
//the agent's name
private String name;
//the agent's id number
private String id;
//the agent's support specialty
private String specialty;
//the agent's average turnaround time in whole minutes
private int time;
/**
* Fully parameterized constructor for Agent.
*
* #param name - this agent's name
* #param id - this agent's id number
* #param specialty - this agent's support specialty
* #param time - this agent's average turnaround time in minutes
*/
public Agent(String name, String id, String specialty, int time)
{
this.name = name;
this.id = id;
this.specialty = specialty;
this.time = time;
}
/**
* Create a copy of this Agent.
*
* #return a new Agent that is a copy of this one
*/
public Agent copy()
{
Agent copyAgent = new Agent(name, id, specialty, time);
return copyAgent;
}
/**
* Accessor for name.
*
* #return this agent's name
*/
public String getName()
{
return name;
}
/**
* Accessor for id number.
*
* #return this agent's id number
*/
public String getID()
{
return id;
}
/**
* Accessor for specialty.
*
* #return this agent's support specialty
*/
public String getSpecialty()
{
return specialty;
}
/**
* Accessor for average turnaround time.
*
* #return this agent's average turnaround time
*/
public int getTime()
{
return time;
}
/**
* Builds a string representation of this agent's data.
*
* #return this agent's data
*/
public String toString()
{
String str = "Agents's name: " + this.name
+ "/nAgent's ID: " + this.id
+ "/nSupport specialty: " + this.specialty
+ "/nAverage service time:" + this.time;
return str;
}
}
Client class:
/**
* Client.java
*
* This class is part of Programming
* Assignment 6 - Tech Support System
* for CS1440 Summer 2014.
*
* #author Brandon C. Eason
* #author
* #version
*/
/**
* This class represents a client profile in
* the tech support system with information
* about the client and the client's
* computer system.
*/
public class Client
{
//the client's full name
private String name;
//the client's phone number
private String phone;
//the client's computer type
private String computer;
//true if a premium client
private boolean premium;
/**
* Fully parameterized constructor for
* Client.
*
* #param name - this client's name
* #param phone - this client's phone number
* #param computer - this client's computer type
* #param premium - whether client has premium service
*/
public Client(String name, String phone, String computer, boolean premium)
{
this.name = name;
this.phone = phone;
this.computer = computer;
this.premium = premium;
}
/**
* Constructor for when computer type is not specified.
*
* #param name - this client's name
* #param phone - this client's phone
* #param premium - whether client has premium service
*/
public Client(String name, String phone, boolean premium)
{
this(name, phone, "Unspecified", premium);
}
/**
* Constructor that creates a copy of the Client passed in.
*
* #param object - the client to be copied
*/
public Client(Client object)
{
name = object.name;
computer = object.computer;
phone = object.phone;
premium = object.premium;
}
/**
* Accessor for name.
*
* #return this client's name
*/
public String getName()
{
return name;
}
/**
* Accessor for phone.
*
* #return this client's phone number
*/
public String getPhone()
{
return phone;
}
/**
* Accessor for computer type.
*
* #return this client's computer type
*/
public String getComputer()
{
return computer;
}
/**
* Determine if this client receives
* premium service.
*
* #return true if this client receives premium service.
*/
public boolean hasPremium()
{
return premium;
}
/**
* Builds a printable String representation of
* this client's data.
*
* #return this client's data
*/
public String toString()
{
String service;
if(this.premium)
{
service = "Premium";
}
else
{
service = "Basic";
}
String str = "Client's name: " + this.name
+ "/nClient's phone: " + this.phone
+ "/nCSystem type: " + this.computer
+ "/nService:" + service;
return str;
}
/**
* Determine if this Client is the same as another.
* Client's are equal if they have the same name,
* phone number, computer type, and service level.
*
* #param other - the other Client
* #return true if this client equals the other
*/
public boolean equals(Client other)
{
if(name.equals(other.name) && phone.equals(other.phone)
&& computer.equals(other.computer) && premium == (other.premium))
{
return true;
}
else
{
return false;
}
}
}
This class processes the tickets by reading request from a file, assigning them to agents, and writing them to a report that beings with the date. There is always agents on duty for each specialization(Mac, Windows PC, or any system).
TicketSupportSystem class:
/**
* TechSupportSystem.java
*
* This class is part of Programming
* Assignment 6 - Tech Support System
* for CS1440 Summer 2013.
*
* #author Brandon C. Eason
* #author
* #version
*/
import java.util.Scanner;
import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.File;
import java.io.IOException;
/**
* This class runs a tech support system
* which reads in tickets entered by clients,
* assigns them to an agent to be resolved,
* and writes them to a report.
*/
public class TechSupportSystem
{
//for keyboard input
private Scanner keyboard;
//for file input
private Scanner input;
//for writing to a file
private PrintWriter output;
//for appending to a file
private FileWriter append;
//for opening files
private File inputFile;
//today's date
private String date;
//first agent
private Agent agentOne;
//second agent
private Agent agentTwo;
//third agent
private Agent agentThree;
//the number of tickets assigned
private int numTickets;
/**
* Constructor for TechSupportSystem.
*
* #throws IOException - file not found
*/
public TechSupportSystem() throws IOException
{
System.out.println("------------------------------\n");
System.out.println("Welcome to Ticket Manager Lite\n");
System.out.println("------------------------------\n");
keyboard = new Scanner(System.in);
getDate();
getAgents();
enableWriting();
processTickets();
output.close();
System.out.println("\nTotal tickets processed: "
+ Ticket.getNumTickets());
System.out.println("\nTotal tickets assigned: " + numTickets);
}
/**
* Get today's date from the dispatcher (the user of this system).
*/
private void getDate()
{
date = "";
while (date.length() != 8)
{
System.out.print("Enter today's date(MMDDYYYY format): ");
date = keyboard.nextLine();
}
}
/**
* Verify file for opening and open it.
*
* #param fileName - the file to be opened
* #return true if valid file
*/
private boolean openFile(String fileName)
{
inputFile = new File(fileName);
return inputFile.exists();
}
/**
* Prepare file writing.
*
* #throws IOException - file not found
*/
private void enableWriting() throws IOException
{
append = new FileWriter("report.txt", true);
output = new PrintWriter(append);
output.println("--------------------------");
output.println("Ticket Report for " + date);
output.println("--------------------------\r\n");
}
/**
* Read in agents on duty.
*
* #throws IOException - file not found
*/
private void getAgents() throws IOException
{
String fileName;
do
{
System.out.print("Enter the name of the agent duty file: ");
fileName = keyboard.nextLine();
} while (!openFile(fileName));
input = new Scanner(inputFile);
agentOne = readAgent();
agentTwo = readAgent();
agentThree = readAgent();
input.close();
}
/**
* Read a single agent.
*
* #return the agent that was read
*/
private Agent readAgent()
{
String name = input.nextLine();
String id = input.nextLine();
String specialty = input.nextLine();
int time = input.nextInt();
input.nextLine();
return new Agent(name, id, specialty, time);
}
/**
* Read in the day's tickets from a file, two
* at a time, check for duplicates, assign the
* tickets to an agent, and write them to a report.
*
* #throws IOException - file not found
*/
private void processTickets() throws IOException
{
String fileName;
Ticket currentTicket;
Ticket lastTicket = null;
do
{
System.out.print("Enter the name of the ticket file: ");
fileName = keyboard.nextLine();
} while (!openFile(fileName));
System.out.println();
input = new Scanner(inputFile);
while (input.hasNext())
{
currentTicket = readTicket();
if (lastTicket == null || !currentTicket.equals(lastTicket))
{
assign(currentTicket);
output.println(currentTicket + "\r\n");
}
lastTicket = currentTicket;
}
input.close();
}
/**
* Read in a single ticket.
*
* #return the ticket that was read in
*/
private Ticket readTicket()
{
Client requester;
String description;
int minutes;
requester = readClient();
description = input.nextLine();
minutes = input.nextInt();
input.nextLine();
return new Ticket(date, description, minutes, requester);
}
/**
* Read in a single client.
*
* #return the client that was read in
*/
private Client readClient()
{
String name;
String phone;
String computer;
String premium;
boolean hasPremium = false;
name = input.nextLine();
phone = input.nextLine();
computer = input.nextLine();
premium = input.nextLine();
if (premium.equals("Premium"))
{
hasPremium = true;
}
if (computer.length() == 0)
{
return new Client(name, phone, hasPremium);
}
else
{
return new Client(name, phone, computer, hasPremium);
}
}
/**
* Assign a ticket to an agent.
*
* #param ticket - the ticket to be assigned
*/
private void assign(Ticket ticket)
{
Client requester = ticket.getClient();
String computer = requester.getComputer();
Agent solver;
if (agentOne.getSpecialty().equals(computer))
{
solver = agentOne;
}
else if (agentTwo.getSpecialty().equals(computer))
{
solver = agentTwo;
}
else
{
solver = agentThree;
}
ticket.setAgent(solver);
System.out.println("Ticket assigned to " + solver.getName() + ".");
numTickets++;
}
/**
* Starts the tech support system.
*
* #param args - not used
* #throws IOException - file not found
*/
public static void main(String[] args) throws IOException
{
new TechSupportSystem();
}
}
Everything compiles right but when I run it, it has an error after I tell it the files and it reads them. The error it gives is:
------------------------------
Welcome to Ticket Manager Lite
------------------------------
Enter today's date(MMDDYYYY format): 12121221
Enter the name of the agent duty file: agents.txt
Enter the name of the ticket file: tickets.txt
Ticket assigned to Dee.
Exception in thread "main" java.lang.NullPointerException
at Ticket.prediction(Ticket.java:153)
at Ticket.toString(Ticket.java:210)
at java.lang.String.valueOf(String.java:2979)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at TechSupportSystem.processTickets(TechSupportSystem.java:178)
at TechSupportSystem.<init>(TechSupportSystem.java:62)
at TechSupportSystem.main(TechSupportSystem.java:275)
Press any key to continue . . .
So I know it has something to do with the null value assigned to lastTicket in the TechSupportSystem class and somethings skewed in the prediction. I just dont know what to change that would make it work with the whole processTickets and prediction methods. Ive been staring at this code for hours and just keep brain-farting on what the actual issue is. Could someone atleast point me in the right direction?
Thanks for your time on this long post.
**Edit***
I fixed the nullexception error. When I run it everything works fine it seems. It reports the correct info to the report.txt but the thing that is messing up is it isnt assigning a solver. The solver is null for every ticket.
I assume its something wrong with my
/**
* Assign this ticket to an agent. The method makes a copy of the
* Agent parameter and sets the solver field to that copy.
*
* #param solver - the agent this ticket is assigned to
*/
public void setAgent(Agent solver)
{
String name = solver.getName();
String id = solver.getID();
String specialty = solver.getSpecialty();
int time = solver.getTime();
solver = new Agent(name, id, specialty, time);
}
just cant figure out what.
Most likely you are trying to print a Ticket without a solver. i.e. the solver is null
public String prediction() {
if(solver == null) return "no solver";
BTW if you use a debugger, you should be able to see what the problem is in a few minutes. I suggest you learn how to use it as it will save you hours of frustration.
The simplest workaround is to trap the exception until you can fix it.
String predict;
try {
predict = prediction();
} catch (Exception e) {
predict = e.toString();
}
data += "\r\n" + predict;
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!!!