/* Copyright (C) 2003-2011 JabRef contributors.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// created by : ?
//
// modified : r.nagel 2.09.2004
// - new SearcherThread.setFinish() method
// - replace thread.sleep in run() by wait() and notify() mechanism
package net.sf.jabref;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.SwingUtilities;
import net.sf.jabref.undo.NamedCompound;
import net.sf.jabref.undo.UndoableRemoveEntry;
import spin.Spin;
public class DuplicateSearch extends Thread {
BasePanel panel;
BibtexEntry[] bes;
final Vector<BibtexEntry[]> duplicates = new Vector<BibtexEntry[]>();
boolean autoRemoveExactDuplicates = false;
public DuplicateSearch(BasePanel bp) {
panel = bp;
}
public void run() {
final NamedCompound ce = new NamedCompound(Globals.lang("duplicate removal"));
int duplicateCounter = 0;
autoRemoveExactDuplicates = false;
panel.output(Globals.lang("Searching for duplicates..."));
Object[] keys = panel.database.getKeySet().toArray();
if ((keys == null) || (keys.length < 2))
return;
bes = new BibtexEntry[keys.length];
for (int i=0; i<keys.length; i++)
bes[i] = panel.database.getEntryById((String)keys[i]);
SearcherThread st = new SearcherThread();
st.setPriority(Thread.MIN_PRIORITY);
st.start();
int current = 0;
final ArrayList<BibtexEntry> toRemove = new ArrayList<BibtexEntry>();
while (!st.finished() || (current < duplicates.size()))
{
if (current >= duplicates.size() )
{
// wait until the search thread puts something into duplicates vector
// or finish its work
synchronized(duplicates)
{
try
{
duplicates.wait();
}
catch (Exception e) {}
}
} else // duplicates found
{
BibtexEntry[] be = duplicates.get(current);
current++;
if (!toRemove.contains(be[0]) && !toRemove.contains(be[1])) {
// Check if they are exact duplicates:
boolean askAboutExact = false;
if (DuplicateCheck.compareEntriesStrictly(be[0], be[1]) > 1) {
if (autoRemoveExactDuplicates) {
toRemove.add(be[1]);
duplicateCounter++;
continue;
} else {
askAboutExact = true;
}
}
DuplicateCallBack cb = new DuplicateCallBack(panel.frame, be[0], be[1],
askAboutExact ? DuplicateResolverDialog.DUPLICATE_SEARCH_WITH_EXACT :
DuplicateResolverDialog.DUPLICATE_SEARCH);
((CallBack)(Spin.over(cb))).update();
duplicateCounter++;
int answer = cb.getSelected();
if ((answer == DuplicateResolverDialog.KEEP_UPPER)
|| (answer == DuplicateResolverDialog.AUTOREMOVE_EXACT)) {
toRemove.add(be[1]);
if (answer == DuplicateResolverDialog.AUTOREMOVE_EXACT)
autoRemoveExactDuplicates = true; // Remember choice
} else if (answer == DuplicateResolverDialog.KEEP_LOWER) {
toRemove.add(be[0]);
} else if (answer == DuplicateResolverDialog.BREAK) {
st.setFinished(); // thread killing
current = Integer.MAX_VALUE;
duplicateCounter--; // correct counter
}
}
}
}
final int dupliC = duplicateCounter;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Now, do the actual removal:
if (toRemove.size() > 0) {
for (Iterator<BibtexEntry> iterator = toRemove.iterator(); iterator.hasNext();) {
BibtexEntry entry = iterator.next();
panel.database.removeEntry(entry.getId());
ce.addEdit(new UndoableRemoveEntry(panel.database, entry, panel));
}
panel.markBaseChanged();
}
panel.output(Globals.lang("Duplicate pairs found") + ": " + duplicates.size()
+" " +Globals.lang("pairs processed") +": " +dupliC );
ce.end();
panel.undoManager.addEdit(ce);
}
});
}
class SearcherThread extends Thread {
private boolean finished = false;
public void run() {
for (int i = 0; (i < bes.length - 1) && !finished ; i++) {
for (int j = i + 1; (j < bes.length) && !finished ; j++) {
boolean eq = DuplicateCheck.isDuplicate(bes[i], bes[j]);
// If (suspected) duplicates, add them to the duplicates vector.
if (eq)
{
synchronized (duplicates)
{
duplicates.add( new BibtexEntry[] {bes[i], bes[j]} ) ;
duplicates.notifyAll(); // send wake up all
}
}
}
}
finished = true;
// if no duplicates found, the graphical thread will never wake up
synchronized(duplicates)
{
duplicates.notifyAll();
}
}
public boolean finished() {
return finished;
}
// Thread cancel option
// no synchronized used because no "realy" critical situations expected
public void setFinished()
{
finished = true ;
}
}
class DuplicateCallBack implements CallBack {
private int reply = -1;
DuplicateResolverDialog diag;
private JabRefFrame frame;
private BibtexEntry one;
private BibtexEntry two;
private int dialogType;
public DuplicateCallBack(JabRefFrame frame, BibtexEntry one, BibtexEntry two,
int dialogType) {
this.frame = frame;
this.one = one;
this.two = two;
this.dialogType = dialogType;
}
public int getSelected() {
return reply;
}
public void update() {
diag = new DuplicateResolverDialog(frame, one, two, dialogType);
diag.setVisible(true);
diag.dispose();
reply = diag.getSelected();
}
}
}