gt:                 String[] altFields = field.split(orSeparator);
context: /*  Copyright (C) 2003-2015 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.
  */
  package net.sf.jabref.model.entry;
  
  import java.text.DateFormat;
  import java.text.FieldPosition;
  import java.text.ParseException;
  import java.text.ParsePosition;
  import java.text.SimpleDateFormat;
  import java.util.Calendar;
  import java.util.Collection;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Optional;
  import java.util.Set;
  import java.util.TreeSet;
  
  import net.sf.jabref.event.FieldChangedEvent;
  import net.sf.jabref.model.database.BibDatabase;
  
  import com.google.common.base.Strings;
  import com.google.common.eventbus.EventBus;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  public class BibEntry implements Cloneable {
      private static final Log LOGGER = LogFactory.getLog(BibEntry.class);
  
      public static final String TYPE_HEADER = "entrytype";
      public static final String KEY_FIELD = "bibtexkey";
      protected static final String ID_FIELD = "id";
      public static final String DEFAULT_TYPE = "misc";
  
      private String id;
      private String type;
      private Map<String, String> fields = new HashMap<>();
  
      // Search and grouping status is stored in boolean fields for quick reference:
      private boolean searchHit;
      private boolean groupHit;
  
      private String parsedSerialization;
  
      /*
       * Marks whether the complete serialization, which was read from file, should be used.
       *
       * Is set to false, if parts of the entry change. This causes the entry to be serialized based on the internal state (and not based on the old serialization)
       */
      private boolean changed;
  
  
      private final EventBus eventBus = new EventBus();
  
  
      /**
       * Constructs a new BibEntry. The internal ID is set to IdGenerator.next()
       */
  
      public BibEntry() {
          this(IdGenerator.next());
      }
  
      /**
       * Constructs a new BibEntry with the given ID and DEFAULT_TYPE
       *
       * @param id The ID to be used
       */
      public BibEntry(String id) {
          this(id, DEFAULT_TYPE);
      }
  
      /**
       * Constructs a new BibEntry with the given ID and given type
       *
       * @param id The ID to be used
       * @param type The type to set. May be null or empty. In that case, DEFAULT_TYPE is used.
       */
      public BibEntry(String id, String type) {
          Objects.requireNonNull(id, "Every BibEntry must have an ID");
  
          this.id = id;
          setType(type);
      }
  
      /**
       * Sets this entry's ID, provided the database containing it
       * doesn't veto the change.
       *
       * @param id The ID to be used
       */
      public void setId(String id) {
          Objects.requireNonNull(id, "Every BibEntry must have an ID");
  
          eventBus.post(new FieldChangedEvent(this, BibEntry.ID_FIELD, id));
          this.id = id;
          changed = true;
      }
  
      /**
       * Returns this entry's ID.
       */
      public String getId() {
          return id;
      }
  
      /**
       * Sets the cite key AKA citation key AKA BibTeX key.
       *
       * Note: This is <emph>not</emph> the internal Id of this entry. The internal Id is always present, whereas the BibTeX key might not be present.
       *
       * @param newCiteKey The cite key to set. Must not be null, may be empty to remove it.
       */
      public void setCiteKey(String newCiteKey) {
          setField(KEY_FIELD, newCiteKey);
      }
  
      /**
       * Returns the cite key AKA citation key AKA BibTeX key, or null if it is not set.
       *
       * Note: this is <emph>not</emph> the internal Id of this entry. The internal Id is always present, whereas the BibTeX key might not be present.
       */
      public String getCiteKey() {
          return fields.get(KEY_FIELD);
      }
  
      public boolean hasCiteKey() {
          return !Strings.isNullOrEmpty(getCiteKey());
      }
  
      /**
       * Returns this entry's type.
       */
      public String getType() {
          return type;
      }
  
      /**
       * Sets this entry's type.
       */
      public void setType(String type) {
          String newType;
          if (Strings.isNullOrEmpty(type)) {
              newType = DEFAULT_TYPE;
          } else {
              newType = type;
          }
  
          // We set the type before throwing the changeEvent, to enable
          // the change listener to access the new value if the change
          // sets off a change in database sorting etc.
          this.type = newType.toLowerCase(Locale.ENGLISH);
          changed = true;
          eventBus.post(new FieldChangedEvent(this, TYPE_HEADER, newType));
      }
  
      /**
       * Sets this entry's type.
       */
      public void setType(EntryType type) {
          this.setType(type.getName());
      }
  
      /**
       * Returns an set containing the names of all fields that are
       * set for this particular entry.
       *
       * @return a set of existing field names
       */
      public Set<String> getFieldNames() {
          return new TreeSet<>(fields.keySet());
      }
  
      /**
       * Returns the contents of the given field, or null if it is not set.
       */
      public String getField(String name) {
          return fields.get(toLowerCase(name));
      }
  
      /**
       * Returns the contents of the given field as an Optional.
       */
      public Optional<String> getFieldOptional(String name) {
          return Optional.ofNullable(fields.get(toLowerCase(name)));
      }
  
      /**
       * Returns true if the entry has the given field, or false if it is not set.
       */
      public boolean hasField(String name) {
          return fields.containsKey(toLowerCase(name));
      }
  
      private String toLowerCase(String fieldName) {
          Objects.requireNonNull(fieldName, "field name must not be null");
  
          return fieldName.toLowerCase(Locale.ENGLISH);
      }
  
      /**
       * Returns the contents of the given field, its alias or null if both are
       * not set.
       * <p>
       * The following aliases are considered (old bibtex <-> new biblatex) based
       * on the BibLatex documentation, chapter 2.2.5:
       * address 		<-> location
       * annote			<-> annotation
       * archiveprefix 	<-> eprinttype
       * journal 		<-> journaltitle
       * key				<-> sortkey
       * pdf 			<-> file
       * primaryclass 	<-> eprintclass
       * school 			<-> institution
       * These work bidirectional.
       * <p>
       * Special attention is paid to dates: (see the BibLatex documentation,
       * chapter 2.3.8)
       * The fields 'year' and 'month' are used if the 'date'
       * field is empty. Conversely, getFieldOrAlias("year") also tries to
       * extract the year from the 'date' field (analogously for 'month').
       */
      public String getFieldOrAlias(String name) {
          String fieldValue = getField(toLowerCase(name));
  
          if (!Strings.isNullOrEmpty(fieldValue)) {
              return fieldValue;
          }
  
          // No value of this field found, so look at the alias
          String aliasForField = EntryConverter.FIELD_ALIASES.get(name);
  
          if (aliasForField != null) {
              return getField(aliasForField);
          }
  
          // Finally, handle dates
          if ("date".equals(name)) {
              String year = getField("year");
              MonthUtil.Month month = MonthUtil.getMonth(getField("month"));
              if (year != null) {
                  if (month.isValid()) {
                      return year + '-' + month.twoDigitNumber;
                  } else {
                      return year;
                  }
              }
          }
          if ("year".equals(name) || "month".equals(name)) {
              String date = getField("date");
              if (date == null) {
                  return null;
              }
  
              // Create date format matching dates with year and month
              DateFormat df = new DateFormat() {
  
                  static final String FORMAT1 = "yyyy-MM-dd";
                  static final String FORMAT2 = "yyyy-MM";
                  final SimpleDateFormat sdf1 = new SimpleDateFormat(FORMAT1);
                  final SimpleDateFormat sdf2 = new SimpleDateFormat(FORMAT2);
  
  
                  @Override
                  public StringBuffer format(Date dDate, StringBuffer toAppendTo, FieldPosition fieldPosition) {
                      throw new UnsupportedOperationException();
                  }
  
                  @Override
                  public Date parse(String source, ParsePosition pos) {
                      if ((source.length() - pos.getIndex()) == FORMAT1.length()) {
                          return sdf1.parse(source, pos);
                      }
                      return sdf2.parse(source, pos);
                  }
              };
  
              try {
                  Date parsedDate = df.parse(date);
                  Calendar calendar = Calendar.getInstance();
                  calendar.setTime(parsedDate);
                  if ("year".equals(name)) {
                      return Integer.toString(calendar.get(Calendar.YEAR));
                  }
                  if ("month".equals(name)) {
                      return Integer.toString(calendar.get(Calendar.MONTH) + 1); // Shift by 1 since in this calendar Jan = 0
                  }
              } catch (ParseException e) {
                  // So not a date with year and month, try just to parse years
                  df = new SimpleDateFormat("yyyy");
  
                  try {
                      Date parsedDate = df.parse(date);
                      Calendar calendar = Calendar.getInstance();
                      calendar.setTime(parsedDate);
                      if ("year".equals(name)) {
                          return Integer.toString(calendar.get(Calendar.YEAR));
                      }
                  } catch (ParseException e2) {
                      LOGGER.warn("Could not parse entry " + name, e2);
                      return null; // Date field not in valid format
                  }
              }
          }
          return null;
      }
  
      /**
       * Sets a number of fields simultaneously. The given HashMap contains field
       * names as keys, each mapped to the value to set.
       */
      public void setField(Map<String, String> fields) {
          Objects.requireNonNull(fields, "fields must not be null");
  
          fields.forEach(this::setField);
      }
  
      /**
       * Set a field, and notify listeners about the change.
       *
       * @param name  The field to set.
       * @param value The value to set.
       */
      public void setField(String name, String value) {
          Objects.requireNonNull(name, "field name must not be null");
          Objects.requireNonNull(value, "field value must not be null");
  
          if (value.isEmpty()) {
              clearField(name);
              return;
          }
  
          String fieldName = toLowerCase(name);
  
          if (BibEntry.ID_FIELD.equals(fieldName)) {
              throw new IllegalArgumentException("The field name '" + name + "' is reserved");
          }
  
          changed = true;
  
          fields.put(fieldName, value);
          eventBus.post(new FieldChangedEvent(this, fieldName, value));
      }
  
      /**
       * Remove the mapping for the field name, and notify listeners about
       * the change.
       *
       * @param name The field to clear.
       */
      public void clearField(String name) {
          String fieldName = toLowerCase(name);
  
          changed = true;
  
          if (BibEntry.ID_FIELD.equals(fieldName)) {
              throw new IllegalArgumentException("The field name '" + name + "' is reserved");
          }
          fields.remove(fieldName);
          eventBus.post(new FieldChangedEvent(this, fieldName, null));
      }
  
      /**
       * Determines whether this entry has all the given fields present. If a non-null
       * database argument is given, this method will try to look up missing fields in
       * entries linked by the "crossref" field, if any.
       *
       * @param allFields An array of field names to be checked.
       * @param database  The database in which to look up crossref'd entries, if any. This
       *                  argument can be null, meaning that no attempt will be made to follow crossrefs.
       * @return true if all fields are set or could be resolved, false otherwise.
       */
      public boolean allFieldsPresent(List<String> allFields, BibDatabase database) {
          final String orSeparator = "/";
  
          for (String field : allFields) {
              String fieldName = toLowerCase(field);
              // OR fields
              if (fieldName.contains(orSeparator)) {
  