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
46 private BibtexEntryType _type;
47 private Map _fields = new HashMap();
48 VetoableChangeSupport _changeSupport = new VetoableChangeSupport(this);
50 // Search and grouping status is stored in boolean fields for quick reference:
51 private boolean searchHit, groupHit;
53 public BibtexEntry(String id)
55 this(id, BibtexEntryType.OTHER);
58 public BibtexEntry(String id, BibtexEntryType type)
62 throw new NullPointerException("Every BibtexEntry must have an ID");
70 * Returns an array describing the optional fields for this entry.
72 public String[] getOptionalFields()
74 return _type.getOptionalFields();
78 * Returns an array describing the required fields for this entry.
80 public String[] getRequiredFields()
82 return _type.getRequiredFields();
86 * Returns an array describing general fields.
88 public String[] getGeneralFields() {
89 return _type.getGeneralFields();
93 * Returns an array containing the names of all fields that are
94 * set for this particular entry.
96 public Object[] getAllFields() {
97 return _fields.keySet().toArray();
101 * Returns a string describing the required fields for this entry.
103 public String describeRequiredFields()
105 return _type.describeRequiredFields();
109 * Returns true if this entry contains the fields it needs to be
112 public boolean hasAllRequiredFields()
114 return _type.hasAllRequiredFields(this);
118 * Returns this entry's type.
120 public BibtexEntryType getType()
126 * Sets this entry's type.
128 public void setType(BibtexEntryType type)
132 throw new NullPointerException(
133 "Every BibtexEntry must have a type. Instead of null, use type OTHER");
140 * Prompts the entry to call BibtexEntryType.getType(String) with
141 * its current type name as argument, and sets its type according
142 * to what is returned. This method is called when a user changes
143 * the type customization, to make sure all entries are set with
145 * @return true if the entry could find a type, false if not (in
146 * this case the type will have been set to
147 * BibtexEntryType.TYPELESS).
149 public boolean updateType() {
150 BibtexEntryType newType = BibtexEntryType.getType(_type.getName());
151 if (newType != null) {
155 _type = BibtexEntryType.TYPELESS;
160 * Sets this entry's ID, provided the database containing it
161 * doesn't veto the change.
163 public void setId(String id) throws KeyCollisionException {
167 NullPointerException("Every BibtexEntry must have an ID");
172 firePropertyChangedEvent("id", _id, id);
174 catch (PropertyVetoException pv)
176 throw new KeyCollisionException("Couldn't change ID: " + pv);
183 * Returns this entry's ID.
185 public String getId()
191 * Returns the contents of the given field, or null if it is not set.
193 public Object getField(String name) {
194 return _fields.get(name);
197 public String getCiteKey() {
198 return (_fields.containsKey(Globals.KEY_FIELD) ?
199 (String)_fields.get(Globals.KEY_FIELD) : null);
203 * Sets the given field to the given value.
205 public void setField(HashMap fields){
206 _fields.putAll(fields);
209 public void setField(String name, Object value) {
211 if ("id".equals(name)) {
212 throw new IllegalArgumentException("The field name '" + name +
216 // This mechanism is probably not really necessary.
217 //Object normalValue = FieldTypes.normalize(name, value);
220 Object oldValue = _fields.get(name);
223 /* The first event is no longer needed, so the following comment doesn't apply
226 // First throw an empty event that just signals that this entry
227 // is about to change. This is needed, so the EntrySorter can
228 // remove the entry from its TreeSet. After a sort-sensitive
229 // field changes, the entry will not be found by the TreeMap,
230 // so without this event it would be impossible to reinsert this
231 // entry to keep everything sorted properly.
232 firePropertyChangedEvent(null, null, null);
235 // We set the field before throwing the changeEvent, to enable
236 // the change listener to access the new value if the change
237 // sets off a change in database sorting etc.
238 _fields.put(name, value);
239 firePropertyChangedEvent(name, oldValue, value);
240 } catch (PropertyVetoException pve) {
241 // Since we have already made the change, we must undo it since
242 // the change was rejected:
243 _fields.put(name, oldValue);
244 throw new IllegalArgumentException("Change rejected: " + pve);
250 * Removes the mapping for the field name.
252 public void clearField(String name) {
254 if ("id".equals(name)) {
255 throw new IllegalArgumentException("The field name '" + name +
258 Object oldValue = _fields.get(name);
259 _fields.remove(name);
261 firePropertyChangedEvent(name, oldValue, "");
262 } catch (PropertyVetoException pve) {
263 throw new IllegalArgumentException("Change rejected: " + pve);
269 protected boolean allFieldsPresent(String[] fields) {
270 for (int i = 0; i < fields.length; i++) {
271 if (getField(fields[i]) == null) {
279 private void firePropertyChangedEvent(String fieldName, Object oldValue,
280 Object newValue) throws PropertyVetoException
282 _changeSupport.fireVetoableChange(new PropertyChangeEvent(this,
283 fieldName, oldValue, newValue));
287 * Adds a VetoableChangeListener, which is notified of field
288 * changes. This is useful for an object that needs to update
289 * itself each time a field changes.
291 public void addPropertyChangeListener(VetoableChangeListener listener)
293 _changeSupport.addVetoableChangeListener(listener);
297 * Removes a property listener.
299 public void removePropertyChangeListener(VetoableChangeListener listener)
301 _changeSupport.removeVetoableChangeListener(listener);
305 * Write this entry to the given Writer, with the given FieldFormatter.
306 * @param write True if this is a write, false if it is a display. The write will
307 * not include non-writeable fields if it is a write, otherwise non-displayable fields
308 * will be ignored. Refer to GUIGlobals for isWriteableField(String) and
309 * isDisplayableField(String).
311 public void write(Writer out, FieldFormatter ff, boolean write) throws IOException {
312 // Write header with type and bibtex-key.
313 out.write("@"+_type.getName().toUpperCase()+"{");
315 String str = Util.shaveString((String)getField(GUIGlobals.KEY_FIELD));
316 out.write(((str == null) ? "" : str)+","+Globals.NEWLINE);
317 HashMap written = new HashMap();
318 written.put(GUIGlobals.KEY_FIELD, null);
319 // Write required fields first.
320 String[] s = getRequiredFields();
321 if (s != null) for (int i=0; i<s.length; i++) {
322 writeField(s[i], out, ff);
323 written.put(s[i], null);
325 // Then optional fields.
326 s = getOptionalFields();
327 if (s != null) for (int i=0; i<s.length; i++) {
328 if (!written.containsKey(s[i])) { // If field appears both in req. and opt. don't repeat.
329 writeField(s[i], out, ff);
330 written.put(s[i], null);
333 // Then write remaining fields in alphabetic order.
334 TreeSet remainingFields = new TreeSet();
335 for (Iterator i = _fields.keySet().iterator(); i.hasNext(); ) {
336 String key = (String)i.next();
337 boolean writeIt = (write ? GUIGlobals.isWriteableField(key) :
338 GUIGlobals.isDisplayableField(key));
339 if (!written.containsKey(key) && writeIt)
340 remainingFields.add(key);
342 for (Iterator i = remainingFields.iterator(); i.hasNext(); )
343 writeField((String)i.next(),out,ff);
345 // Finally, end the entry.
346 out.write("}"+Globals.NEWLINE);
349 private void writeField(String name, Writer out,
350 FieldFormatter ff) throws IOException {
351 Object o = getField(name);
353 out.write(" "+name+" = ");
356 out.write(ff.format(o.toString(), name));
357 } catch (Throwable ex) {
358 throw new IOException
359 (Globals.lang("Error in field")+" '"+name+"': "+ex.getMessage());
361 //Util.writeField(name, o, out);
362 out.write(","+Globals.NEWLINE);
367 * Returns a clone of this entry. Useful for copying.
369 public Object clone() {
370 BibtexEntry clone = new BibtexEntry(_id, _type);
371 clone._fields = (Map)((HashMap)_fields).clone();
376 public String toString() {
377 return getType().getName()+":"+getField(Globals.KEY_FIELD);
380 public boolean isSearchHit() {
384 public void setSearchHit(boolean searchHit) {
385 this.searchHit = searchHit;
388 public boolean isGroupHit() {
392 public void setGroupHit(boolean groupHit) {
393 this.groupHit = groupHit;
397 * @param maxCharacters The maximum number of characters (additional
398 * characters are replaced with "..."). Set to 0 to disable truncation.
399 * @return A short textual description of the entry in the format:
400 * Author1, Author2: Title (Year)
402 public String getAuthorTitleYear(int maxCharacters) {
403 String[] s = new String[] {
404 (String) getField("author"),
405 (String) getField("title"),
406 (String) getField("year")};
407 for (int i = 0; i < s.length; ++i)
410 String text = s[0] + ": \"" + s[1] + "\" (" + s[2] + ")";
411 if (maxCharacters <= 0 || text.length() <= maxCharacters)
413 return text.substring(0, maxCharacters + 1) + "...";