package org.jabref.logic.bibtex.comparator; import java.text.Collator; import java.text.ParseException; import java.text.RuleBasedCollator; import java.util.Comparator; import java.util.Locale; import java.util.Objects; import java.util.Optional; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.FieldName; import org.jabref.model.entry.FieldProperty; import org.jabref.model.entry.InternalBibtexFields; import org.jabref.model.entry.Month; import org.jabref.model.metadata.SaveOrderConfig; import org.jabref.model.strings.StringUtil; /** * A comparator for BibEntry fields */ public class FieldComparator implements Comparator<BibEntry> { private static final Collator COLLATOR = getCollator(); enum FieldType { NAME, TYPE, YEAR, MONTH, OTHER } private final String[] field; private final String fieldName; private final FieldType fieldType; private final boolean isNumeric; private final int multiplier; public FieldComparator(String field) { this(field, false); } public FieldComparator(SaveOrderConfig.SortCriterion sortCriterion) { this(sortCriterion.field, sortCriterion.descending); } public FieldComparator(String field, boolean descending) { this.fieldName = Objects.requireNonNull(field); this.field = fieldName.split(FieldName.FIELD_SEPARATOR); fieldType = determineFieldType(); isNumeric = InternalBibtexFields.isNumeric(this.field[0]); multiplier = descending ? -1 : 1; } private static Collator getCollator() { try { return new RuleBasedCollator( ((RuleBasedCollator) Collator.getInstance()).getRules().replace("<'\u005f'", "<' '<'\u005f'")); } catch (ParseException e) { return Collator.getInstance(); } } private FieldType determineFieldType() { if (BibEntry.TYPE_HEADER.equals(this.field[0])) { return FieldType.TYPE; } else if (InternalBibtexFields.getFieldProperties(this.field[0]).contains(FieldProperty.PERSON_NAMES)) { return FieldType.NAME; } else if (FieldName.YEAR.equals(this.field[0])) { return FieldType.YEAR; } else if (FieldName.MONTH.equals(this.field[0])) { return FieldType.MONTH; } else { return FieldType.OTHER; } } private String getField(BibEntry entry) { for (String aField : field) { Optional<String> o = entry.getFieldOrAliasLatexFree(aField); if (o.isPresent()) { return o.get(); } } return null; } @Override public int compare(BibEntry e1, BibEntry e2) { String f1; String f2; if (fieldType == FieldType.TYPE) { // Sort by type. f1 = e1.getType(); f2 = e2.getType(); } else { // If the field is author or editor, we rearrange names so they are // sorted according to last name. f1 = getField(e1); f2 = getField(e2); } // Catch all cases involving null: if ((f1 == null) && (f2 == null)) { return 0; } else if (f1 == null) { return multiplier; } else if (f2 == null) { return -multiplier; } // Now we know that both f1 and f2 are != null if (fieldType == FieldType.NAME) { f1 = AuthorList.fixAuthorForAlphabetization(f1); f2 = AuthorList.fixAuthorForAlphabetization(f2); } else if (fieldType == FieldType.YEAR) { Integer f1year = StringUtil.intValueOfOptional(f1).orElse(0); Integer f2year = StringUtil.intValueOfOptional(f2).orElse(0); int comparisonResult = Integer.compare(f1year, f2year); return comparisonResult * multiplier; } else if (fieldType == FieldType.MONTH) { int month1 = Month.parse(f1).map(Month::getNumber).orElse(-1); int month2 = Month.parse(f2).map(Month::getNumber).orElse(-1); return Integer.compare(month1, month2) * multiplier; } if (isNumeric) { Optional<Integer> i1 = StringUtil.intValueOfOptional(f1); Optional<Integer> i2 = StringUtil.intValueOfOptional(f2); if ((i2.isPresent()) && (i1.isPresent())) { // Ok, parsing was successful. Update f1 and f2: return i1.get().compareTo(i2.get()) * multiplier; } else if (i1.isPresent()) { // The first one was parseable, but not the second one. // This means we consider one < two return -1 * multiplier; } else if (i2.isPresent()) { // The second one was parseable, but not the first one. // This means we consider one > two return 1 * multiplier; } // Else none of them were parseable, and we can fall back on comparing strings. } String ours = f1.toLowerCase(Locale.ENGLISH); String theirs = f2.toLowerCase(Locale.ENGLISH); return COLLATOR.compare(ours, theirs) * multiplier; } /** * Returns the field this Comparator compares by. * * @return The field name. */ public String getFieldName() { return fieldName; } }