java - JProgressBar while method is running - java

The Find() method is a sample of my method that makes a search of a word into some files.
I call it from a Button_Start mouseclicked event.
Here is my code:
public void Find (String word) {
List_files_directories (directory.getAbsolutePath(), files);
// print list
textpane.append(java.awt.Color.cyan, "Files in the choosen directory\n");
for (int j=0; j<files.size(); j++) {
if (files.get(j) != null)
textpane.append( java.awt.Color.PINK, "\n" + files.get(j) );
}
// from listarray to array
String[] array_files = new String[files.size()];
array_files = files.toArray(array_files);
int j;
for (j=0; j<array_files.length; j++) {
if (array_files[j] != null) {
if (array_files[j].contains("1")) {
function1 ( array_files[j], word );
}
else if ( array_files[j].contains("2") )
{
function2 ( array_files[j], word);
}
else if ( array_files[j].contains("3") )
{
function3 ( array_CARTELLE[j], word );
}
}
}
}
private void Button_StartMouseClicked(java.awt.event.MouseEvent evt) {
java.util.Timer().schedule(
new java.util.TimerTask() {
#Override
public void run() {
Find(word_from_textfield);
}
},
10
);
}
I need to add 2 things:
I'd like to make a JProgressBar while Find() method is running.
In this case I need to adopt a standard to set set the bar progress
i.e. based on the files list length, I don't know.
I'd like to set WAIT_CURSOR while Find() method is running, so until
progress bar reaches 100%.
I tried this for my request 1):
public void doWork() {
Worker worker = new Worker();
worker.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
ProgressBar1.setValue((Integer) evt.getNewValue());
}
}
});
worker.execute();
}
public class Worker extends SwingWorker<Object, Object> {
#Override
protected Object doInBackground() throws Exception {
for (int index = 1; index <= 1000; index++) {
int progress = Math.round(((float) index / 1000) * 100f);
setProgress(progress);
Thread.sleep(100);
}
return null;
}
}
In this I call doWork() at the beginning of my Find() method.
The problem is that I don't know how to synchronize my method duration and the progress bar length.
Besides I don't know if it's the best way to reach my aim.
Could you please help me giving an example according to my code/situation?
Thanks
edit: here is my goal: My method prints on a JTextPane ( textpane.append() ) all files from the user directory, after that it search a word in each file.
I want to use a JProgressBar while it's printing on the JTextPane and it has to be synchronized with the process duration.
If it's possibile the print has to be timed:
Example:
// Set Mouse cursor to Wait Status
// execute Find():
// the JProgressBar has to follow this progress of the print on JTextPane.
Files in the choosen directory:
file1
// print on JTextPane after 1 second
file2
// print on JTextPane after 1 second
file3
// print on JTextPane after 1 second
word found in file1!
// print on JTextPane after 1 second
word not found in file2
// print on JTextPane after 1 second
word found in file3
// print on JTextPane after 1 second
// restore mouse cursor to default

What's publish()? I don't understand what I have to do.
The methods publish() and process() let the SwingWorker reliably communicate interim results from the background thread to the event dispatch thread (EDT). In this example, process() displays the current result of a long-running calculation; in this related example, process() also updates a chart's data model.
Addendum: As #mKorbel notes, you can leverage generic type checking by using a more specific type, e.g. SwingWorker<Double, Double>.

I think you need to integrate the work done by Find() into the SwingWorker. Then you could set your progress bar after processing each file.
#Override
protected Object doInBackground() throws Exception {
for (int j=0; j<files.size(); j++) {
if (files.get(j) != null)
textpane.append( java.awt.Color.PINK, "\n" + files.get(j) );
}
// from listarray to array
String[] array_files = new String[files.size()];
array_files = files.toArray(array_files);
int j;
for (j=0; j<array_files.length; j++) {
if (array_files[j] != null) {
if (array_files[j].contains("1")) {
function1 ( array_files[j], word );
}
else if ( array_files[j].contains("2") )
{
function2 ( array_files[j], word);
}
else if ( array_files[j].contains("3") )
{
function3 ( array_CARTELLE[j], word );
}
// THIS IS THE NEW BIT
setProgress((int)(100 * (float) j / files.size()));
}
}

Related

Do I use a SwingWorker?

While my code sends http calls I want it to alter the JLabel on a window (basically showing approximately how much time is left before the http calls end). I have been looking into SwingWorkers due to another question I asked here, but I'm not sure how I use it. The code I am writing basically has a loop to send the calls, each time timing how long it takes to run the call, calculates the approximate time left and then sends this to the JLabel (NB the JLabel is in a different instantiated object).
Most SwingWorker examples show a function continuing in the background that is not affected by the worker thread (e.g. a counter based entirely on time rather than being altered by the code). If this is the case then isn't the alteration of the JLabel just part of the worker thread as it the code runs through new loop -> calculate time & make call -> alter JLabel? I'm probably wrong but then how do I have the JLabel altered by the code rather than a independent thread?
One of my issues was that when I initially set my code up there was nothing changing in the JLabel.
Here is my code:
package transcription.windows;
import javax.swing.*;
import java.awt.*;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
public class PleaseWaitWindow {
private JLabel pleaseWaitJLabel = new JLabel("Please wait");
private GridBagConstraints containerGbc = new GridBagConstraints();
private Container contentPaneContainer = new Container();
private JFrame pleaseWaitJFrame;
public JLabel getPleaseWaitJLabel() {
return pleaseWaitJLabel;
}
public JFrame setPleaseWaitWindow() {
pleaseWaitJFrame = new JFrame();
contentPaneContainer = setContentPane();
pleaseWaitJFrame.setContentPane(contentPaneContainer);
pleaseWaitJFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
pleaseWaitJFrame.setTitle("");
pleaseWaitJFrame.setSize(350, 150);
pleaseWaitJFrame.setLocationRelativeTo(null);
pleaseWaitJFrame.setVisible(true);
return pleaseWaitJFrame;
}
private Container setContentPane() {
containerGbc.insets.bottom = 1;
containerGbc.insets.top = 2;
containerGbc.insets.right = 1;
containerGbc.insets.left = 1;
containerGbc.weightx = 1;
containerGbc.weighty = 1;
contentPaneContainer.setLayout(new GridBagLayout());
contentPaneContainer.setSize(800, 700);
setPleaseWaitJLabel();
return contentPaneContainer;
}
private void setPleaseWaitJLabel() {
containerGbc.gridx = 2;
containerGbc.gridy = 5;
containerGbc.gridwidth = 2;
containerGbc.gridheight = 1;
contentPaneContainer.add(pleaseWaitJLabel, containerGbc);
}
public void setJLabelDisplay(String displayTime) {
pleaseWaitJLabel.setText(displayTime);
}
public void closeWindow() {
pleaseWaitJFrame.dispose();
}
}
Method that is part of the ServiceUpload class:
public String cuttingLoop(String mpBase64Piece, String jobName, String email) {
Integer numberOfPiecesMinusEnd = (int) Math.ceil(mpBase64Piece.length() / 500000.0);
List<String> base64List = new ArrayList<>();
for (int i = 0; i < numberOfPiecesMinusEnd; i++) {
if (mpBase64Piece.length() >= 500000) {
base64List.add(mpBase64Piece.substring(0, 500000));
mpBase64Piece = mpBase64Piece.substring(500000);
}
}
base64List.add(mpBase64Piece);
pleaseWaitWindow = new PleaseWaitWindow();
pleaseWaitWindow.setPleaseWaitWindow();
for (int n = 0; n < base64List.size(); n++) {
numberOfLoopsLeft = numberOfPiecesMinusEnd - n;
Stopwatch stopwatch = null;
String tag;
Stopwatch.createStarted();
if (base64List.get(n) != null) {
if (n == 0) {
tag = "start";
} else if (n == base64List.size() - 1) {
tag = "end";
} else {
tag = "middle";
}
stopwatch = Stopwatch.createStarted();
response = providerUpload.executeUploadHttp(base64List.get(n), jobName, tag, email);
stopwatch.stop();
}
long oneLoopTime = stopwatch.elapsed(TimeUnit.SECONDS);
pleaseWaitWindow.setJLabelDisplay(numberOfLoopsLeft*oneLoopTime+" seconds remaining");
LOGGER.info("complete");
}
pleaseWaitWindow.closeWindow();
return response;
}
One of my issues was the code did not show the 'JLabel' when a 'SwingWorker' isn't used with the above code.
It's best you split up your code into areas of responsibilities. Let's go with three: 1. the worker (ie the upload); 2. the display (ie the JLabel update); 3. integration of the two (the first two are independent of each other, so you'll need something to tie them together).
Abstracting from the actual work, you can use standard interfaces. The first one is just a Runnable, ie not taking any parameters and not returning anything. The second one is a Consumer<String> because it takes a String (to display) but doesn't return anything. The third will be your main control.
Let's start with the control because that's simple:
Consumer<String> display = createDisplay();
Runnable worker = createWorker();
CompletableFuture.runAsync(worker);
This will start the worker in a separate Thread which is what it sounds like you want.
So here's your uploader:
Consumer<String> display = // tbd, see below
Runnable worker = () -> {
String[] progress = {"start", "middle", "finish"};
for (String pr : progress) {
display.accept(pr);
Thread.sleep(1000); // insert your code here
}
}
Note that this worker actually does depend on the consumer; that is somewhat "unclean", but will do.
Now for the display. Having defined it as a Consumer<String>, it's abstract enough that we can just print the progress on the console.
Consumer<String> display = s -> System.out.printf("upload status: %s%n", s);
You however want to update a JLabel; so the consumer would look like
Consumer<String> display = s -> label.setText(s);
// for your code
s -> pleaseWaitWindow.getPleaseWaitLabel().setText(s);
Your actual question
So if you do that, you will notice that your label text doesn't get updated as you expect. That is because the label.setText(s) gets executed in the thread in which the worker is running; it needs to be inserted in the Swing thread. That's where the SwingWorker comes in.
The SwingWorker has a progress field which is what you can use for your labels; it also has a doInBackground() which is your actual upload worker thread. So you end up with
class UploadSwingWorker {
public void doInBackground() {
for(int i = 0; i < 3; i++) {
setProgress(i);
Thread.sleep(1000); // again, your upload code
}
}
}
So how does that update your label? The setProgress raises a PropertyChangeEvent you can intercept; this done using a PropertyChangeListener with the signature
void propertyChange(PropertyChangeEvent e)
This is a functional interface, so you can implement this with a lambda, in your case
String[] displays = {"start", "middle", "finish"};
updateLabelListener = e -> {
int index = ((Integer) e.getNewValue()).intValue(); // the progress you set
String value = displays[index];
label.setText(value);
}
and you can add it to the SwingWorker using
SwingWorker uploadWorker = new UploadSwingWorker();
uploadWorker.addPropertyChangeListener(updateLabelListener);
uploadWorker.execute(); // actually start the worker
Simpler
Note that I myself have never used a SwingWorker this way. The much simpler way to get around the problem that the GUI is not updated from within your worker thread is to call the GUI update using SwingUtilities.invokeLater().
Coming back to the initial Consumer<String> I brought up, you can do
Consumer<String> display = s -> SwingUtilities.invokeLater(
() -> pleaseWaitWindow.getPleaseWaitLabel().setText(s)
);
and that should do. This allows you to keep your worker in the more abstract Runnable and use the usual scheduling mechanisms to run it (ExecutorService.submit() or CompletableFuture.runAsync() for example), while still allowing to update the GUI on a similarly simple level.

Enclose try-catch in a public method and invoke when needed

How to enclose try-catch in a public method and invoke when needed.
I have try-catch in one screen code. I want to invoke it from another screen by calling a method to it that stands public(to the entire application).
Is it possible ? If so how.
Please guide.
Re edits:
As seen in the below code, second tab pane implementation has been shown,please ignore the syntactic differences one may find with native java(This has been implemented for Blackberry JDE). Implementation constructs remain the same hence please overlook the differences and suggest a logical solution to the problem being faced.
// setup the second tab
vfm = new VerticalFieldManager(
Field.USE_ALL_HEIGHT | Field.USE_ALL_WIDTH |
Manager.VERTICAL_SCROLL | Manager.NO_HORIZONTAL_SCROLL );
//Initialize grid for publishing results
grid.add(new LabelField("Name")
{
public void paint(Graphics graphics)
{
graphics.setColor(Color.CYAN);
super.paint(graphics);
}
});
grid.add(new LabelField("Total")
{
public void paint(Graphics graphics)
{
graphics.setColor(Color.CYAN);
super.paint(graphics);
}
});
grid.setColumnPadding(100);
grid.setRowPadding(20);
//TRY CATCH STARTS HERE
try
{
//Open or create the database
Database db = DatabaseFactory.openOrCreate("database1.db");
Statement statementG55 = db.createStatement("CREATE TABLE IF NOT EXISTS GTemp4(gname TEXT,gbal INTEGER)");
statementG55.prepare();
statementG55.execute();
statementG55.close();
Statement statementG56 = db.createStatement("SELECT gname,gbal FROM GTemp4 ORDER BY ROWID DESC");
statementG56.prepare();
statementG56.execute();
Cursor c = statementG56.getCursor();
//Get to the row of grid
for (int i =1; i < grid.getRowCount(); i++)
{
System.out.println("Inside for first loops");
//Get to the column of grid
for (int j = 0; j < grid.getColumnCount() ; j++)
{
System.out.println("Inside for second loops");
//Get to the row of temp4 table
while(c.next())
{
System.out.println("Inside while");
Row r;
r = c.getRow();
for (int k = 1; k >=0; k--)
{
System.out.println("Inside for loops");
if(k==0)
{
System.out.println("Retrieving Names");
grid.insert(new LabelField(r.getString(k))
{
public void paint(Graphics graphics)
{
graphics.setColor(Color.GOLD);
super.paint(graphics);
}
},i,j);
}
else
{
System.out.println("Retrieving other values");
String p = "" + r.getObject(k);
grid.insert(new LabelField(p)
{
public void paint(Graphics graphics)
{
graphics.setColor(Color.GOLD);
super.paint(graphics);
}
},i,j);
}
grid.setBackground(BackgroundFactory.createLinearGradientBackground(Color.MIDNIGHTBLUE,Color.STEELBLUE,Color.MIDNIGHTBLUE,Color.STEELBLUE));
}
System.out.println("Exiting while");
}
System.out.println("Exiting sec for");
break;
}
System.out.println("Exiting first for");
break;
}
statementG56.close();
db.close();
}
catch(Exception e)
{
System.out.println( e.getMessage() );
e.printStackTrace();
}
vfm.add(grid);
nullFld = new NullField( Field.FOCUSABLE );
hfm = new HorizontalFieldManager();
hfm.add( nullFld );
hfm.add( myLbl );
pane = new Pane( hfm, vfm );
model.addPane( pane );
A big thanks to everyone below who have made a suggestion.
Your question is still cryptic. I am assuming that you have some code which does some searching and then, it publishes the results to some JPanel called pane2. What you want is that once the Search button is pressed, you call the code.
You could have a method like so:
public void doSomeSearching(...) throws Exception //This will allow you to remove the try/catch block from within the method and be able to catch any exceptions in the layer above.
{
//Do the searching
//Update panel2
}
Then, what you need to do is to add an action listener to your button. This will allow the code to be executed once the button is clicked. (You can find more information on ActionListeners here).
JButton btnSearch = new JButton("Search");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
try
{
doSomeSearching();
}
catch (Exception e)
{
//Do something when exception is raised.
}
}
});
This should allow you to trigger the search functionality when you click the button and handle any exceptions which should arise.
EDIT:
another try-catch included within pane2 to publish(It keeps running from the start,i.e,doesnt wait for action listener of search button to be executed)`
Having something loop indefinitely is something which should be ideally avoided since this consumes CPU cycles while basically doing nothing. This usually increases the resources your application consumes and might also cause your GUI to hang. If I where you I would have some method which updates panel2 which you than call once you have done your searching.
That being said, you could have some intermediary variable, such as a string which contains whatever you need to print and your search method continuously updates this intermediary variable.
This approach is not recommended.
Instead of placing a generic surrounding try-catch block around your method, you should consider adding the "throws" clause to the declaration of each method you want to handle this way, and then handle all those exception in a higher layer.

Create a simple queue with Java threads

I'm trying to create a simple queue with Java Thread that would allow a loop, say a for loop with 10 iterations, to iterate n (< 10) threads at a time and wait until those threads are finished before continuing to iterate.
Here's a better way to illustrate my problem:
for (int i = 1; i <= 10; i++) {
new Thread ( do_some_work() );
if ( no_available_threads ) {
wait_until_available_threads();
}
}
do_some_work() {
// do something that takes a long time
}
Basically what I want to do is a copy of this: Thread and Queue
How can I achieve this the most painless way?
I would use the Java 5 Executors instead of rolling your own. Something like the following:
ExecutorService service = Executors.newFixedThreadPool(10);
// now submit our jobs
service.submit(new Runnable() {
public void run() {
do_some_work();
}
});
// you can submit any number of jobs and the 10 threads will work on them
// in order
...
// when no more to submit, call shutdown, submitted jobs will continue to run
service.shutdown();
// now wait for the jobs to finish
service.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
Use the Executors, as recommended by the others. However, if you want the fun of doing it yourself, try something like this. (Take care. I wrote it in Notepad and there's some Exceptions you'll need to catch even if I got everything else right. Notepad's poor at catching coding errors.) This is more a concept than an actual solution to anything, but the idea could be generally useful.
private ConcurrentLinkedQueue<MyThread> tQueue =
new ConcurrentLinkedQueue<MyThread>();
class MyThread extends Thread {
public Runnable doSomething;
public void run() {
// Do the real work.
doSomething();
// Clean up and make MyThread available again.
tQueue.add( mythread );
// Might be able to avoid this synch with clever code.
// (Don't synch if you know no one's waiting.)
// (But do that later. Much later.)
synchronized (tQueue) {
// Tell them the queue is no longer empty.
tQueue.notifyAll();
}
}
}
Elsewhere:
// Put ten MyThreads in tQueue.
for (int i = 0; i < 10; i++) tQueue.add( new MyThread() );
// Main Loop. Runs ten threads endlessly.
for (;;) {
MyThread t = tQueue.poll();
if (t == null) {
// Queue empty. Sleep till someone tells us it's not.
do {
// There's a try-catch combo missing here.
synchonized( tQueue ) { tQueue.wait() };
t = tQueue.poll();
} while (t == null) break; // Watch for fake alert!
}
t.doSomething = do_some_work;
t.start();
}
Also, note the clever use of ConcurrentLinkedQueue. You could use something else like ArrayList or LinkedList, but you'd need to synchronize them.
see java.util.concurrent and especially Executors and ExecutorService
Crate Logger.class :
public class Logger extends Thread {
List<String> queue = new ArrayList<String>();
private final int MAX_QUEUE_SIZE = 20;
private final int MAX_THREAD_COUNT = 10;
#Override
public void start() {
super.start();
Runnable task = new Runnable() {
#Override
public void run() {
while (true) {
String message = pullMessage();
Log.d(Thread.currentThread().getName(), message);
// Do another processing
}
}
};
// Create a Group of Threads for processing
for (int i = 0; i < MAX_THREAD_COUNT; i++) {
new Thread(task).start();
}
}
// Pulls a message from the queue
// Only returns when a new message is retrieves
// from the queue.
private synchronized String pullMessage() {
while (queue.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
}
}
return queue.remove(0);
}
// Push a new message to the tail of the queue if
// the queue has available positions
public synchronized void pushMessage(String logMsg) {
if (queue.size() < MAX_QUEUE_SIZE) {
queue.add(logMsg);
notifyAll();
}
}
}
Then insert bellow code in your main class :
Logger logger =new Logger();
logger.start();
for ( int i=0; i< 10 ; i++) {
logger.pushMessage(" DATE : "+"Log Message #"+i);
}

Applying a Counter to Item in List

I have a list that is poulated via a local text file. I have the following code that simple prints the selected item/items when the button is click.
private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) {
int[] selection = jList3.getSelectedIndices();
// selection.toString();
for (int i = 0; i < selection.length; i++){
Object selString = jList3.getModel().getElementAt(selection[i]);
System.out.println(selString);
}
}
Instead of printing the item I would like each button click on each object to be recorded somehow. I have no idea what kind of component, method etc to implement. Any guidance is appreciated.
My end result will be something similar to this.
System.out.println(SelString has been clicked X amount of times);
Use a hash map with your objects (selString) as keys, and a counter as value. Something like:
private Map<Object, Integer> buttonMap = new HashMap<Object, Integer>
private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) {
Integer counter = null;
int[] selection = jList3.getSelectedIndices();
for (int i = 0; i < selection.length; i++){
Object selString = jList3.getModel().getElementAt(selection[i]);
counter = buttonMap.get(selString);
if(counter == null) {
buttonMap.put(selString, new Integer(0));
}
buttonMap.put(selString, new Integer(counter.intValue() + 1));
System.out.println(selString + " has been clicked " + buttonMap.get(selString) + " times.");
}
}
You can use PropertyChangeSupport to notify each time jList items are clicked, besides you should create a listener to receive events notification (through propertyChangeSupport.addPropertyChangeListener).
Once there, you can get the event properties such as the property name and property's new value which will be selected item on jList3 in this case, for counting how many times some item was clicked, you could use a HashMap, setting the key as the item index of jList and the associated value how many time the item has been clicked:
PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
HashMap<Integer, Integer> clickCounter = new HashMap<Integer, Integer>();
public NewJFrame() {
initComponents();
propertyChangeSupport.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("selectedIndex")) {
System.out.println("Selected index: " + evt.getNewValue());
System.out.println("Selected text: " + jList3.getModel().getElementAt(evt.getNewValue()));
if (clickCounter.containsKey((Integer) evt.getNewValue())) {
clickCounter.put((Integer) evt.getNewValue(), clickCounter.get((Integer) evt.getNewValue()) + 1);
} else {
clickCounter.put((Integer) evt.getNewValue(), 1);
}
}
}
});
}
private void jList1MouseClicked(java.awt.event.MouseEvent evt) {
// TODO add your handling code here:
propertyChangeSupport.firePropertyChange("selectedIndex", -1, jList3.getSelectedIndex());
}
At any time you can retrive how many times certian item was clicked accessing clickCounter
I would suggest using an inner class which holds whatever object you are currently putting into the JList and adding a counter member variable as well as overriding the toString().
class MyListItem
{
int selectionCount;
Object listItem; //may consider generics here, but left them out cause they can be tricky
public MyListItem(Object item)
{
selectionCount=0;
listItem=item;
}
public void incrementSelectionCount()
{
selectionCount++;
}
public String toString()
{
return listItem.toString() + " has been clicked " + selectionCount + " times.");
}
}
Then in your action listener
private void jButton5ActionPerformed(java.awt.event.ActionEvent evt)
{
int[] selection = jList3.getSelectedIndices();
for (int selectedIndex : selection)
{
Object selString = jList3.getModel().getElementAt(selectedIndex);
if(selString instanceOf MyListItem)
{
MyListItem selItem = (MyListItem) selString;
selItem.incrementSelectionCount();
System.out.println(selString);
}
}
}
This should save time on look ups, boxing, etc. Also, this helps keep things sane as the project grows since MyListItem can be grown to deal with all types of actions you may want in the future in case you want different things to happen for things other than button presses. The basic idea here is that the MyListItem should keep track of everything you are interested in tracking so you don't need multiple lists and even worse, to remember to add an item to both a JList and a HashMap or any other data structure. This way it's either on every data structure it needs to be or not at all.

Java SAX parser progress monitoring

I'm writing a SAX parser in Java to parse a 2.5GB XML file of wikipedia articles. Is there a way to monitor the progress of the parsing in Java?
Thanks to EJP's suggestion of ProgressMonitorInputStream, in the end I extended FilterInputStream so that ChangeListener can be used to monitor the current read location in term of bytes.
With this you have finer control, for example to show multiple progress bars for parallel reading of big xml files. Which is exactly what I did.
So, a simplified version of the monitorable stream:
/**
* A class that monitors the read progress of an input stream.
*
* #author Hermia Yeung "Sheepy"
* #since 2012-04-05 18:42
*/
public class MonitoredInputStream extends FilterInputStream {
private volatile long mark = 0;
private volatile long lastTriggeredLocation = 0;
private volatile long location = 0;
private final int threshold;
private final List<ChangeListener> listeners = new ArrayList<>(4);
/**
* Creates a MonitoredInputStream over an underlying input stream.
* #param in Underlying input stream, should be non-null because of no public setter
* #param threshold Min. position change (in byte) to trigger change event.
*/
public MonitoredInputStream(InputStream in, int threshold) {
super(in);
this.threshold = threshold;
}
/**
* Creates a MonitoredInputStream over an underlying input stream.
* Default threshold is 16KB, small threshold may impact performance impact on larger streams.
* #param in Underlying input stream, should be non-null because of no public setter
*/
public MonitoredInputStream(InputStream in) {
super(in);
this.threshold = 1024*16;
}
public void addChangeListener(ChangeListener l) { if (!listeners.contains(l)) listeners.add(l); }
public void removeChangeListener(ChangeListener l) { listeners.remove(l); }
public long getProgress() { return location; }
protected void triggerChanged( final long location ) {
if ( threshold > 0 && Math.abs( location-lastTriggeredLocation ) < threshold ) return;
lastTriggeredLocation = location;
if (listeners.size() <= 0) return;
try {
final ChangeEvent evt = new ChangeEvent(this);
for (ChangeListener l : listeners) l.stateChanged(evt);
} catch (ConcurrentModificationException e) {
triggerChanged(location); // List changed? Let's re-try.
}
}
#Override public int read() throws IOException {
final int i = super.read();
if ( i != -1 ) triggerChanged( location++ );
return i;
}
#Override public int read(byte[] b, int off, int len) throws IOException {
final int i = super.read(b, off, len);
if ( i > 0 ) triggerChanged( location += i );
return i;
}
#Override public long skip(long n) throws IOException {
final long i = super.skip(n);
if ( i > 0 ) triggerChanged( location += i );
return i;
}
#Override public void mark(int readlimit) {
super.mark(readlimit);
mark = location;
}
#Override public void reset() throws IOException {
super.reset();
if ( location != mark ) triggerChanged( location = mark );
}
}
It doesn't know - or care - how big the underlying stream is, so you need to get it some other way, such as from the file itself.
So, here goes the simplified sample usage:
try (
MonitoredInputStream mis = new MonitoredInputStream(new FileInputStream(file), 65536*4)
) {
// Setup max progress and listener to monitor read progress
progressBar.setMaxProgress( (int) file.length() ); // Swing thread or before display please
mis.addChangeListener( new ChangeListener() { #Override public void stateChanged(ChangeEvent e) {
SwingUtilities.invokeLater( new Runnable() { #Override public void run() {
progressBar.setProgress( (int) mis.getProgress() ); // Promise me you WILL use MVC instead of this anonymous class mess!
}});
}});
// Start parsing. Listener would call Swing event thread to do the update.
SAXParserFactory.newInstance().newSAXParser().parse(mis, this);
} catch ( IOException | ParserConfigurationException | SAXException e) {
e.printStackTrace();
} finally {
progressBar.setVisible(false); // Again please call this in swing event thread
}
In my case the progresses raise nicely from left to right without abnormal jumps. Adjust threshold for optimum balance between performance and responsiveness. Too small and the reading speed can more then double on small devices, too big and the progress would not be smooth.
Hope it helps. Feel free to edit if you found mistakes or typos, or vote up to send me some encouragements! :D
Use a javax.swing.ProgressMonitorInputStream.
You can get an estimate of the current line/column in your file by overriding the method setDocumentLocator of org.xml.sax.helpers.DefaultHandler/BaseHandler. This method is called with an object from which you can get an approximation of the current line/column when needed.
Edit: To the best of my knowledge, there is no standard way to get the absolute position. However, I am sure some SAX implementations do offer this kind of information.
Assuming you know how many articles you have, can't you just keep a counter in the handler? E.g.
public void startElement (String uri, String localName,
String qName, Attributes attributes)
throws SAXException {
if(qName.equals("article")){
counter++
}
...
}
(I don't know whether you are parsing "article", it's just an example)
If you don't know the number of article in advance, you will need to count it first. Then you can print the status nb tags read/total nb of tags, say each 100 tags (counter % 100 == 0).
Or even have another thread monitor the progress. In this case, you might want to synchronize access to the counter, but not necessary given that it doesn't need to be really accurate.
My 2 cents
I'd use the input stream position. Make your own trivial stream class that delegates/inherits from the "real" one and keeps track of bytes read. As you say, getting the total filesize is easy. I wouldn't worry about buffering, lookahead, etc. - for large files like these it's chickenfeed. On the other hand, I'd limit the position to "99%".

Categories