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.
31 package net.sf.jabref;
33 import net.sf.jabref.export.FieldFormatter;
34 import java.beans.PropertyChangeEvent;
35 import java.beans.PropertyVetoException;
36 import java.beans.VetoableChangeListener;
37 import java.beans.VetoableChangeSupport;
43 public class BibtexEntry
45 public final static String ID_FIELD = "id";
47 private BibtexEntryType _type;
48 private Map _fields = new HashMap();
49 VetoableChangeSupport _changeSupport = new VetoableChangeSupport(this);
51 // Search and grouping status is stored in boolean fields for quick reference:
52 private boolean searchHit, groupHit;
54 public BibtexEntry(String id)
56 this(id, BibtexEntryType.OTHER);
59 public BibtexEntry(String id, BibtexEntryType type)
63 throw new NullPointerException("Every BibtexEntry must have an ID");
71 * Returns an array describing the optional fields for this entry.
73 public String[] getOptionalFields()
75 return _type.getOptionalFields();
79 * Returns an array describing the required fields for this entry.
81 public String[] getRequiredFields()
83 return _type.getRequiredFields();
87 * Returns an array describing general fields.
89 public String[] getGeneralFields() {
90 return _type.getGeneralFields();
94 * Returns an array containing the names of all fields that are
95 * set for this particular entry.
97 public Object[] getAllFields() {
98 return _fields.keySet().toArray();
102 * Returns a string describing the required fields for this entry.
104 public String describeRequiredFields()
106 return _type.describeRequiredFields();
110 * Returns true if this entry contains the fields it needs to be
113 public boolean hasAllRequiredFields()
115 return _type.hasAllRequiredFields(this);
119 * Returns this entry's type.
121 public BibtexEntryType getType()
127 * Sets this entry's type.
129 public void setType(BibtexEntryType type)
133 throw new NullPointerException(
134 "Every BibtexEntry must have a type. Instead of null, use type OTHER");
141 * Prompts the entry to call BibtexEntryType.getType(String) with
142 * its current type name as argument, and sets its type according
143 * to what is returned. This method is called when a user changes
144 * the type customization, to make sure all entries are set with
146 * @return true if the entry could find a type, false if not (in
147 * this case the type will have been set to
148 * BibtexEntryType.TYPELESS).
150 public boolean updateType() {
151 BibtexEntryType newType = BibtexEntryType.getType(_type.getName());
152 if (newType != null) {
156 _type = BibtexEntryType.TYPELESS;
161 * Sets this entry's ID, provided the database containing it
162 * doesn't veto the change.
164 public void setId(String id) throws KeyCollisionException {
168 NullPointerException("Every BibtexEntry must have an ID");
173 firePropertyChangedEvent(ID_FIELD, _id, id);
175 catch (PropertyVetoException pv)
177 throw new KeyCollisionException("Couldn't change ID: " + pv);
184 * Returns this entry's ID.
186 public String getId()
192 * Returns the contents of the given field, or null if it is not set.
194 public Object getField(String name) {
195 return _fields.get(name);
198 public String getCiteKey() {
199 return (_fields.containsKey(BibtexFields.KEY_FIELD) ?
200 (String)_fields.get(BibtexFields.KEY_FIELD) : null);
204 * Sets a number of fields simultaneously. The given HashMap contains field
205 * names as keys, each mapped to the value to set.
206 * WARNING: this method does not notify change listeners, so it should *NOT*
207 * be used for entries that are being displayed in the GUI. Furthermore, it
208 * does not check values for content, so e.g. empty strings will be set as such.
210 public void setField(Map fields){
211 _fields.putAll(fields);
215 * Set a field, and notify listeners about the change.
217 * @param name The field to set.
218 * @param value The value to set.
220 public void setField(String name, Object value) {
222 if (ID_FIELD.equals(name)) {
223 throw new IllegalArgumentException("The field name '" + name +
227 // This mechanism is probably not really necessary.
228 //Object normalValue = FieldTypes.normalize(name, value);
231 Object oldValue = _fields.get(name);
234 // We set the field before throwing the changeEvent, to enable
235 // the change listener to access the new value if the change
236 // sets off a change in database sorting etc.
237 _fields.put(name, value);
238 firePropertyChangedEvent(name, oldValue, value);
239 } catch (PropertyVetoException pve) {
240 // Since we have already made the change, we must undo it since
241 // the change was rejected:
242 _fields.put(name, oldValue);
243 throw new IllegalArgumentException("Change rejected: " + pve);
249 * Remove the mapping for the field name, and notify listeners about
252 * @param name The field to clear.
254 public void clearField(String name) {
256 if (ID_FIELD.equals(name)) {
257 throw new IllegalArgumentException("The field name '" + name +
260 Object oldValue = _fields.get(name);
261 _fields.remove(name);
263 firePropertyChangedEvent(name, oldValue, null);
264 } catch (PropertyVetoException pve) {
265 throw new IllegalArgumentException("Change rejected: " + pve);
271 protected boolean allFieldsPresent(String[] fields) {
272 for (int i = 0; i < fields.length; i++) {
273 if (getField(fields[i]) == null) {
281 private void firePropertyChangedEvent(String fieldName, Object oldValue,
282 Object newValue) throws PropertyVetoException
284 _changeSupport.fireVetoableChange(new PropertyChangeEvent(this,
285 fieldName, oldValue, newValue));
289 * Adds a VetoableChangeListener, which is notified of field
290 * changes. This is useful for an object that needs to update
291 * itself each time a field changes.
293 public void addPropertyChangeListener(VetoableChangeListener listener)
295 _changeSupport.addVetoableChangeListener(listener);
299 * Removes a property listener.
301 public void removePropertyChangeListener(VetoableChangeListener listener)
303 _changeSupport.removeVetoableChangeListener(listener);
307 * Write this entry to the given Writer, with the given FieldFormatter.
308 * @param write True if this is a write, false if it is a display. The write will
309 * not include non-writeable fields if it is a write, otherwise non-displayable fields
310 * will be ignored. Refer to GUIGlobals for isWriteableField(String) and
311 * isDisplayableField(String).
313 public void write(Writer out, FieldFormatter ff, boolean write) throws IOException {
314 // Write header with type and bibtex-key.
315 out.write("@"+_type.getName().toUpperCase()+"{");
317 String str = Util.shaveString((String)getField(BibtexFields.KEY_FIELD));
318 out.write(((str == null) ? "" : str)+","+Globals.NEWLINE);
319 HashMap written = new HashMap();
320 written.put(BibtexFields.KEY_FIELD, null);
321 boolean hasWritten = false;
322 // Write required fields first.
323 String[] s = getRequiredFields();
324 if (s != null) for (int i=0; i<s.length; i++) {
325 hasWritten = hasWritten | writeField(s[i], out, ff, hasWritten);
326 written.put(s[i], null);
328 // Then optional fields.
329 s = getOptionalFields();
330 if (s != null) for (int i=0; i<s.length; i++) {
331 if (!written.containsKey(s[i])) { // If field appears both in req. and opt. don't repeat.
332 //writeField(s[i], out, ff);
333 hasWritten = hasWritten | writeField(s[i], out, ff, hasWritten);
334 written.put(s[i], null);
337 // Then write remaining fields in alphabetic order.
338 TreeSet remainingFields = new TreeSet();
339 for (Iterator i = _fields.keySet().iterator(); i.hasNext(); ) {
340 String key = (String)i.next();
341 boolean writeIt = (write ? BibtexFields.isWriteableField(key) :
342 BibtexFields.isDisplayableField(key));
343 if (!written.containsKey(key) && writeIt)
344 remainingFields.add(key);
346 for (Iterator i = remainingFields.iterator(); i.hasNext(); )
347 hasWritten = hasWritten | writeField((String)i.next(), out, ff, hasWritten);
348 //writeField((String)i.next(),out,ff);
350 // Finally, end the entry.
351 out.write((hasWritten ? Globals.NEWLINE : "")+"}"+Globals.NEWLINE);
355 * Write a single field, if it has any content.
356 * @param name The field name
357 * @param out The Writer to send it to
358 * @param ff A formatter to filter field contents before writing
359 * @param isFirst Indicates whether this is the first field written for
360 * this entry - if not, start by writing a comma and newline
361 * @return true if this field was written, false if it was skipped because
363 * @throws IOException In case of an IO error
365 private boolean writeField(String name, Writer out,
366 FieldFormatter ff, boolean isFirst) throws IOException {
367 Object o = getField(name);
370 out.write(","+Globals.NEWLINE);
371 out.write(" "+name+" = ");
374 out.write(ff.format(o.toString(), name));
375 } catch (Throwable ex) {
376 throw new IOException
377 (Globals.lang("Error in field")+" '"+name+"': "+ex.getMessage());
380 //Util.writeField(name, o, out);
381 //out.write(","+Globals.NEWLINE);
387 * Returns a clone of this entry. Useful for copying.
389 public Object clone() {
390 BibtexEntry clone = new BibtexEntry(_id, _type);
391 clone._fields = (Map)((HashMap)_fields).clone();
396 public String toString() {
397 return getType().getName()+":"+getField(BibtexFields.KEY_FIELD);
400 public boolean isSearchHit() {
404 public void setSearchHit(boolean searchHit) {
405 this.searchHit = searchHit;
408 public boolean isGroupHit() {
412 public void setGroupHit(boolean groupHit) {
413 this.groupHit = groupHit;
417 * @param maxCharacters The maximum number of characters (additional
418 * characters are replaced with "..."). Set to 0 to disable truncation.
419 * @return A short textual description of the entry in the format:
420 * Author1, Author2: Title (Year)
422 public String getAuthorTitleYear(int maxCharacters) {
423 String[] s = new String[] {
424 (String) getField("author"),
425 (String) getField("title"),
426 (String) getField("year")};
427 for (int i = 0; i < s.length; ++i)
430 String text = s[0] + ": \"" + s[1] + "\" (" + s[2] + ")";
431 if (maxCharacters <= 0 || text.length() <= maxCharacters)
433 return text.substring(0, maxCharacters + 1) + "...";