/* 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(); } } }