2 Copyright (C) 2003 David Weitzman, Morten O. Alver
4 All programs in this directory and
5 subdirectories are published under the GNU General Public License as
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23 Further information about the GNU GPL is available at:
24 http://www.gnu.org/copyleft/gpl.ja.html
27 Modified for use in JabRef
34 // modified : r.nagel 23.08.2004
35 // - insert getEntryByKey() methode needed by AuxSubGenerator
37 package net.sf.jabref;
42 import javax.swing.JOptionPane;
44 import net.sf.jabref.groups.GroupSelector;
46 public class BibtexDatabase
48 Map _entries = new Hashtable();
49 String _preamble = null;
50 HashMap _strings = new HashMap();
51 Vector _strings_ = new Vector();
52 Hashtable _autoCompleters = null;
53 Set changeListeners = new HashSet();
54 private BibtexDatabase ths = this;
56 private HashMap allKeys = new HashMap(); // use a map instead of a set since i need to know how many of each key is inthere
58 /* Entries are stored in a HashMap with the ID as key.
59 * What happens if someone changes a BibtexEntry's ID
60 * after it has been added to this BibtexDatabase?
61 * The key of that entry would be the old ID, not the new one.
62 * Use a PropertyChangeListener to identify an ID change
65 private final VetoableChangeListener listener =
66 new VetoableChangeListener()
68 public void vetoableChange(PropertyChangeEvent pce)
69 throws PropertyVetoException
71 if (pce.getPropertyName() == null)
72 fireDatabaseChanged (new DatabaseChangeEvent(ths, DatabaseChangeEvent.CHANGING_ENTRY, (BibtexEntry)pce.getSource()));
73 else if ("id".equals(pce.getPropertyName()))
75 // locate the entry under its old key
77 _entries.remove((String) pce.getOldValue());
79 if (oldEntry != pce.getSource())
81 // Something is very wrong!
82 // The entry under the old key isn't
83 // the one that sent this event.
84 // Restore the old state.
85 _entries.put(pce.getOldValue(), oldEntry);
86 throw new PropertyVetoException("Wrong old ID", pce);
89 if (_entries.get(pce.getNewValue()) != null)
91 _entries.put(pce.getOldValue(), oldEntry);
92 throw new PropertyVetoException
93 ("New ID already in use, please choose another",
97 // and re-file this entry
98 _entries.put((String) pce.getNewValue(),
99 (BibtexEntry) pce.getSource());
101 fireDatabaseChanged (new DatabaseChangeEvent(ths, DatabaseChangeEvent.CHANGED_ENTRY, (BibtexEntry)pce.getSource()));
102 //Util.pr(pce.getSource().toString()+"\n"+pce.getPropertyName()
103 // +"\n"+pce.getNewValue());
109 * Returns the number of entries.
111 public synchronized int getEntryCount()
113 return _entries.size();
117 * Returns a Set containing the keys to all entries.
118 * Use getKeySet().iterator() to iterate over all entries.
120 public synchronized Set getKeySet()
122 return _entries.keySet();
126 * Returns an EntrySorter with the sorted entries from this base,
127 * sorted by the given Comparator.
129 public synchronized EntrySorter getSorter(java.util.Comparator comp) {
130 EntrySorter sorter = new EntrySorter(_entries, comp);
131 addDatabaseChangeListener(sorter);
136 * Just temporary, for testing purposes....
139 public Map getEntryMap() { return _entries; }
142 * Returns the entry with the given ID (-> entry_type + hashcode).
144 public synchronized BibtexEntry getEntryById(String id)
146 return (BibtexEntry) _entries.get(id);
149 public synchronized Collection getEntries() {
150 return _entries.values();
154 * Returns the entry with the given bibtex key.
156 public synchronized BibtexEntry getEntryByKey(String key)
158 BibtexEntry back = null ;
160 int keyHash = key.hashCode() ; // key hash for better performance
162 Set keySet = _entries.keySet();
165 Iterator it = keySet.iterator();
166 boolean loop = it.hasNext() ;
169 String entrieID = (String) it.next() ;
170 BibtexEntry entry = getEntryById(entrieID) ;
171 if ((entry != null) && (entry.getCiteKey() != null))
173 String citeKey = entry.getCiteKey() ;
176 if (keyHash == citeKey.hashCode() )
181 else loop = it.hasNext() ;
182 } else loop = it.hasNext() ;
189 public synchronized BibtexEntry[] getEntriesByKey(String key) {
190 Vector entries = new Vector();
192 for (Iterator it = _entries.entrySet().iterator(); it.hasNext(); ) {
193 entry = (BibtexEntry)((Map.Entry)it.next()).getValue();
194 if (key.equals(entry.getCiteKey()))
197 BibtexEntry[] entryArray = new BibtexEntry[entries.size()];
198 return (BibtexEntry[]) entries.toArray(entryArray);
202 * Inserts the entry, given that its ID is not already in use.
203 * use Util.createId(...) to make up a unique ID for an entry.
205 public synchronized boolean insertEntry(BibtexEntry entry)
206 throws KeyCollisionException
208 String id = entry.getId();
209 if (getEntryById(id) != null)
211 throw new KeyCollisionException(
212 "ID is already in use, please choose another");
215 entry.addPropertyChangeListener(listener);
217 // Possibly add a FieldChangeListener, which is there to add
218 // new words to the autocompleter's dictionary. In case the
219 // entry is non-empty (pasted), update completers.
220 /*if (_autoCompleters != null) {
221 entry.addPropertyChangeListener(new FieldChangeListener
222 (_autoCompleters, entry));
223 Util.updateCompletersForEntry(_autoCompleters,
227 _entries.put(id, entry);
229 fireDatabaseChanged(new DatabaseChangeEvent(this, DatabaseChangeEvent.ADDED_ENTRY, entry));
231 return checkForDuplicateKeyAndAdd(null, entry.getCiteKey(), false);
235 * Removes the entry with the given string.
237 public synchronized BibtexEntry removeEntry(String id)
239 BibtexEntry oldValue = (BibtexEntry) _entries.remove(id);
240 removeKeyFromSet(oldValue.getCiteKey());
242 if (oldValue != null)
244 oldValue.removePropertyChangeListener(listener);
247 fireDatabaseChanged(new DatabaseChangeEvent(this, DatabaseChangeEvent.REMOVED_ENTRY, oldValue));
252 public synchronized boolean setCiteKeyForEntry(String id, String key) {
253 if (!_entries.containsKey(id)) return false; // Entry doesn't exist!
254 BibtexEntry entry = getEntryById(id);
255 String oldKey = entry.getCiteKey();
257 entry.setField(BibtexFields.KEY_FIELD, key);
259 entry.clearField(BibtexFields.KEY_FIELD);
260 return checkForDuplicateKeyAndAdd(oldKey, entry.getCiteKey(), false);
264 * Sets the database's preamble.
266 public synchronized void setPreamble(String preamble)
268 _preamble = preamble;
272 * Returns the database's preamble.
274 public synchronized String getPreamble()
280 * Inserts a Bibtex String at the given index.
282 public synchronized void addString(BibtexString string)
283 throws KeyCollisionException
285 for (java.util.Iterator i=_strings.keySet().iterator(); i.hasNext();) {
286 if (((BibtexString)_strings.get(i.next())).getName().equals(string.getName()))
287 throw new KeyCollisionException("A string with this label already exists,");
290 if (_strings.containsKey(string.getId()))
291 throw new KeyCollisionException("Duplicate BibtexString id.");
293 _strings.put(string.getId(), string);
297 * Removes the string at the given index.
299 public synchronized void removeString(String id) {
304 * Returns a Set of keys to all BibtexString objects in the database.
305 * These are in no sorted order.
307 public Set getStringKeySet() {
308 return _strings.keySet();
312 * Returns the string at the given index.
314 public synchronized BibtexString getString(Object o) {
315 return (BibtexString)(_strings.get(o));
319 * Returns the number of strings.
321 public synchronized int getStringCount() {
322 return _strings.size();
326 * Returns true if a string with the given label already exists.
328 public synchronized boolean hasStringLabel(String label) {
329 for (java.util.Iterator i=_strings.keySet().iterator(); i.hasNext();) {
330 if (((BibtexString)_strings.get(i.next())).getName().equals(label))
337 * Resolves any references to strings contained in this database,
340 public String resolveForStrings(String content) {
341 return resolveContent(content, new HashSet());
345 * If the label represents a string contained in this database, returns
346 * that string's content. Resolves references to other strings, taking
347 * care not to follow a circular reference pattern.
348 * If the string is undefined, returns the label itself.
350 private String resolveString(String label, HashSet usedIds) {
351 for (java.util.Iterator i=_strings.keySet().iterator(); i.hasNext();) {
352 BibtexString string = (BibtexString)_strings.get(i.next());
354 //Util.pr(label+" : "+string.getName());
355 if (string.getName().toLowerCase().equals(label.toLowerCase())) {
357 // First check if this string label has been resolved
358 // earlier in this recursion. If so, we have a
359 // circular reference, and have to stop to avoid
360 // infinite recursion.
361 if (usedIds.contains(string.getId())) {
362 Util.pr("Stopped due to circular reference in strings: "+label);
365 // If not, log this string's ID now.
366 usedIds.add(string.getId());
368 // Ok, we found the string. Now we must make sure we
369 // resolve any references to other strings in this one.
370 String res = string.getContent();
371 res = resolveContent(res, usedIds);
373 // Finished with recursing this branch, so we remove our
375 usedIds.remove(string.getId());
381 // If we get to this point, the string has obviously not been defined locally.
382 // Check if one of the standard BibTeX month strings has been used:
384 if ((o = Globals.MONTH_STRINGS.get(label.toLowerCase())) != null) {
391 private String resolveContent(String res, HashSet usedIds) {
392 //if (res.matches(".*#[-\\^\\:\\w]+#.*")) {
393 if (res.matches(".*#[^#]+#.*")) {
394 StringBuffer newRes = new StringBuffer();
395 int piv = 0, next = 0;
396 while ((next=res.indexOf("#", piv)) >= 0) {
398 // We found the next string ref. Append the text
401 newRes.append(res.substring(piv, next));
402 int stringEnd = res.indexOf("#", next+1);
403 if (stringEnd >= 0) {
404 // We found the boundaries of the string ref,
405 // now resolve that one.
406 String refLabel = res.substring(next+1, stringEnd);
407 String resolved = resolveString(refLabel, usedIds);
408 if (refLabel.equals(resolved)) {
409 // We got just the label in return, so this may not have
410 // been intended as a string label, or it may be a label for
411 // an undefined string. Therefore we prefer to display the #
412 // characters rather than removing them:
413 newRes.append(res.substring(next, stringEnd+1));
415 // The string was resolved, so we display its meaning only,
416 // stripping the # characters signifying the string label:
417 newRes.append(resolved);
420 // We didn't find the boundaries of the string ref. This
421 // makes it impossible to interpret it as a string label.
422 // So we should just append the rest of the text and finish.
423 newRes.append(res.substring(next));
429 if (piv < res.length()-1)
430 newRes.append(res.substring(piv));
431 res = newRes.toString();
436 //##########################################
438 // isDuplicate=checkForDuplicateKeyAndAdd( null, b.getKey() , issueDuplicateWarning);
439 //############################################
440 // if the newkey already exists and is not the same as oldkey it will give a warning
441 // else it will add the newkey to the to set and remove the oldkey
442 public boolean checkForDuplicateKeyAndAdd(String oldKey, String newKey, boolean issueWarning){
443 // Globals.logger(" checkForDuplicateKeyAndAdd [oldKey = " + oldKey + "] [newKey = " + newKey + "]");
445 boolean duplicate=false;
446 if(oldKey==null){// this is a new entry so don't bother removing oldKey
447 duplicate= addKeyToSet( newKey);
449 if(oldKey.equals(newKey)){// were OK because the user did not change keys
451 }else{// user changed the key
453 // removed the oldkey
454 // But what if more than two have the same key?
455 // this means that user can add another key and would not get a warning!
456 // consider this: i add a key xxx, then i add another key xxx . I get a warning. I delete the key xxx. JBM
457 // removes this key from the allKey. then I add another key xxx. I don't get a warning!
458 // i need a way to count the number of keys of each type
459 // hashmap=>int (increment each time)
461 removeKeyFromSet( oldKey);
462 duplicate = addKeyToSet( newKey );
465 if(duplicate==true && issueWarning==true){
466 JOptionPane.showMessageDialog(null, Globals.lang("Warning there is a duplicate key")+":" + newKey ,
467 Globals.lang("Duplicate Key Warning"),
468 JOptionPane.WARNING_MESSAGE);//, options);
475 * Returns the number of occurences of the given key in this database.
477 public int getNumberOfKeyOccurences(String key) {
478 Object o = allKeys.get(key);
482 return ((Integer)o).intValue();
486 //========================================================
487 // keep track of all the keys to warn if there are duplicates
488 //========================================================
489 private boolean addKeyToSet(String key){
490 boolean exists=false;
491 if((key == null) || key.equals(""))
492 return false;//don't put empty key
493 if(allKeys.containsKey(key)){
496 allKeys.put( key, new Integer( ((Integer)allKeys.get(key)).intValue() + 1));// incrementInteger( allKeys.get(key)));
498 allKeys.put( key, new Integer(1));
501 //========================================================
502 // reduce the number of keys by 1. if this number goes to zero then remove from the set
503 // note: there is a good reason why we should not use a hashset but use hashmap instead
504 //========================================================
505 private void removeKeyFromSet(String key){
506 if((key == null) || key.equals("")) return;
507 if(allKeys.containsKey(key)){
508 Integer tI = (Integer)allKeys.get(key); // if(allKeys.get(key) instanceof Integer)
510 allKeys.remove( key);
512 allKeys.put( key, new Integer( ((Integer)tI).intValue() - 1));//decrementInteger( tI ));
518 public void fireDatabaseChanged(DatabaseChangeEvent e) {
519 for (Iterator i=changeListeners.iterator(); i.hasNext();) {
520 ((DatabaseChangeListener)i.next()).databaseChanged(e);
524 public void addDatabaseChangeListener(DatabaseChangeListener l) {
525 changeListeners.add(l);
528 public void removeDatabaseChangeListener(DatabaseChangeListener l) {
529 changeListeners.remove(l);
533 public void setCompleters(Hashtable autoCompleters) {
534 _autoCompleters = autoCompleters;
536 for (Iterator i=getKeySet().iterator(); i.hasNext();) {
537 BibtexEntry be = getEntryById((String)(i.next()));
538 be.addPropertyChangeListener(new FieldChangeListener
539 (autoCompleters, be));
541 Util.updateCompletersForEntry(autoCompleters, be);