Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workaround missing "Optional 2" display #11022

Merged
merged 24 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We fixed an issue where the duplicate check did not take umlauts or other LaTeX-encoded characters into account. [#10744](https://github.com/JabRef/jabref/pull/10744)
- We fixed the colors of the icon on hover for unset special fields. [#10431](https://github.com/JabRef/jabref/issues/10431)
- We fixed an issue where the CrossRef field did not work if autocompletion was disabled [#8145](https://github.com/JabRef/jabref/issues/8145)
- In biblatex mode, JabRef distinguishes between "Optional fields" and "Optional fields 2" again. [#11022](https://github.com/JabRef/jabref/pull/11022)
- We fixed an issue where exporting`@electronic` and `@online` entry types to the Office XMl would duplicate the field `title` [#10807](https://github.com/JabRef/jabref/issues/10807)
- We fixed an issue where the `CommentsTab` was not properly formatted when the `defaultOwner` contained capital or special letters. [#10870](https://github.com/JabRef/jabref/issues/10870)
- We fixed an issue where the `File -> Close library` menu item was not disabled when no library was open. [#10948](https://github.com/JabRef/jabref/issues/10948)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import org.jabref.model.entry.BibEntryType;
import org.jabref.model.entry.field.Field;

/**
* This class is required to check whether a delete button should be displayed at {@link org.jabref.gui.preferences.customentrytypes.CustomEntryTypesTab#setupEntryTypesTable()}
*/
public class CustomEntryTypeViewModel extends EntryTypeViewModel {

public CustomEntryTypeViewModel(BibEntryType entryType, Predicate<Field> isMultiline) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void setValues() {

for (BibEntryType entryType : allTypes) {
EntryTypeViewModel viewModel;
if (entryTypesManager.isCustomType(entryType.getType(), bibDatabaseMode)) {
if (entryTypesManager.isCustomType(entryType, bibDatabaseMode)) {
viewModel = new CustomEntryTypeViewModel(entryType, isMultiline);
} else {
viewModel = new EntryTypeViewModel(entryType, isMultiline);
Expand All @@ -102,23 +102,28 @@ public void setValues() {
public void storeSettings() {
Set<Field> multilineFields = new HashSet<>();
for (EntryTypeViewModel typeViewModel : entryTypesWithFields) {
BibEntryType type = typeViewModel.entryType().getValue();
List<FieldViewModel> allFields = typeViewModel.fields();

BibEntryType type = typeViewModel.entryType().getValue();
EntryType newPlainType = type.getType();

// Collect multilineFields for storage in preferences later
multilineFields.addAll(allFields.stream()
.filter(FieldViewModel::isMultiline)
.map(FieldViewModel::toField)
.map(model -> model.toField(newPlainType))
.toList());

List<OrFields> required = allFields.stream()
.filter(FieldViewModel::isRequired)
.map(FieldViewModel::toField)
.map(model -> model.toField(newPlainType))
.map(OrFields::new)
.collect(Collectors.toList());
List<BibField> fields = allFields.stream().map(FieldViewModel::toBibField).collect(Collectors.toList());

BibEntryType newType = new BibEntryType(type.getType(), fields, required);
entryTypesManager.addCustomOrModifiedType(newType, bibDatabaseMode);
List<BibField> fields = allFields.stream().map(model -> model.toBibField(newPlainType)).collect(Collectors.toList());

BibEntryType newType = new BibEntryType(newPlainType, fields, required);

entryTypesManager.update(newType, bibDatabaseMode);
}

for (var entryType : entryTypesToDelete) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.model.entry.field.FieldPriority;
import org.jabref.model.entry.field.FieldProperty;
import org.jabref.model.entry.types.EntryType;

public class FieldViewModel {

Expand Down Expand Up @@ -55,18 +56,18 @@ public FieldPriority getPriority() {
return priorityProperty.getValue();
}

public Field toField() {
public Field toField(EntryType type) {
// If the field name is known by JabRef, JabRef's casing will win.
// If the field is not known by JabRef (UnknownField), the new casing will be taken.
Field field = FieldFactory.parseField(displayName.getValue());
Field field = FieldFactory.parseField(type, displayName.getValue());
if (multiline.getValue()) {
field.getProperties().add(FieldProperty.MULTILINE_TEXT);
}
return field;
}

public BibField toBibField() {
return new BibField(toField(), priorityProperty.getValue());
public BibField toBibField(EntryType type) {
return new BibField(toField(type), priorityProperty.getValue());
}

@Override
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/jabref/model/entry/BibEntryType.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ public String toString() {
'}';
}

/**
* WARNING! This does not follow the equals contract. Even if this here returns 0, the objects can be different.
*/
@Override
public int compareTo(BibEntryType o) {
return this.getType().getName().compareTo(o.getType().getName());
Expand Down
162 changes: 139 additions & 23 deletions src/main/java/org/jabref/model/entry/BibEntryTypesManager.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package org.jabref.model.entry;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.entry.field.BibField;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.OrFields;
import org.jabref.model.entry.types.BiblatexAPAEntryTypeDefinitions;
import org.jabref.model.entry.types.BiblatexEntryTypeDefinitions;
import org.jabref.model.entry.types.BiblatexSoftwareEntryTypeDefinitions;
Expand All @@ -18,6 +23,7 @@
import org.jabref.model.entry.types.EntryTypeFactory;
import org.jabref.model.entry.types.IEEETranEntryTypeDefinitions;

import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -36,13 +42,28 @@ public class BibEntryTypesManager {
public BibEntryTypesManager() {
}

private InternalEntryTypes getEntryTypes(BibDatabaseMode mode) {
@VisibleForTesting
InternalEntryTypes getEntryTypes(BibDatabaseMode mode) {
return switch (mode) {
case BIBTEX -> BIBTEX_ENTRYTYPES;
case BIBLATEX -> BIBLATEX_ENTRYTYPES;
};
}

/**
* Returns all types known to JabRef. This includes the standard types as well as the customized types
*/
public Collection<BibEntryType> getAllTypes(BibDatabaseMode mode) {
return getEntryTypes(mode).getAllTypes();
}

/**
* Returns all types which are customized (be it a variant of a standard type or a completely new type)
*/
public Collection<BibEntryType> getAllCustomizedTypes(BibDatabaseMode mode) {
return getEntryTypes(mode).getAllCustomizedTypes();
}

/**
* For a given database mode, determine all custom entry types, i.e. types that are not overwritten standard types but real custom types.
* For example, a modified "article" type will not be included in the list, but an entry type like "MyCustomType" will be included.
Expand All @@ -55,12 +76,28 @@ public List<BibEntryType> getAllCustomTypes(BibDatabaseMode mode) {
}

/**
* Returns true if the type is a custom type, or if it is a standard type which has customized fields
* Returns true if the type is a custom type, or if it is a standard type which has different customized fields
*/
public boolean isCustomOrModifiedType(BibEntryType type, BibDatabaseMode mode) {
return getEntryTypes(mode).isCustomOrModifiedType(type);
}

/**
* Required to check if during load of a .bib file the customization of the entry type is different
*
* @return true if the given type is unknown here or is different from the stored one
*/
public boolean isDifferentCustomOrModifiedType(BibEntryType type, BibDatabaseMode mode) {
Optional<BibEntryType> currentlyStoredType = enrich(type.getType(), mode);
if (currentlyStoredType.isEmpty()) {
// new customization
return true;
} else {
// different customization
return !EntryTypeFactory.nameAndFieldsAreEqual(type, currentlyStoredType.get());
}
}

/**
* Sets the given custom entry types for BibTeX and biblatex mode
*/
Expand All @@ -73,6 +110,52 @@ public void addCustomOrModifiedType(BibEntryType entryType, BibDatabaseMode mode
getEntryTypes(mode).addCustomOrModifiedType(entryType);
}

/**
* Updates the internal list. In case the given entry type equals a standard type, it is removed from the list of customized types.
* Otherwise, it is stored as customized.
*/
public void update(BibEntryType entryType, BibDatabaseMode mode) {
InternalEntryTypes entryTypes = getEntryTypes(mode);
if (entryTypes.standardTypes.contains(entryType)) {
// The method to check containment does a deep equals. Thus, different fields lead to a non-containment property
entryTypes.removeCustomOrModifiedEntryType(entryType);
return;
}
if (!entryTypes.isStandardType(entryType)) {
entryTypes.addCustomOrModifiedType(entryType);
}

// Workaround for UI not supporting OrFields
Optional<BibEntryType> standardTypeOpt = entryTypes.standardTypes.stream()
.filter(InternalEntryTypes.typeEquals(entryType.getType()))
.findFirst();
if (standardTypeOpt.isEmpty()) {
LOGGER.error("Standard type not found for {}", entryType.getType());
entryTypes.addCustomOrModifiedType(entryType);
return;
}

BibEntryType standardType = standardTypeOpt.get();
Set<Field> standardRequiredFields = standardType.getRequiredFields().stream()
.map(OrFields::getFields)
.flatMap(Set::stream)
.collect(Collectors.toSet());
Set<BibField> standardOptionalFields = standardType.getOptionalFields();

Set<Field> entryTypeRequiredFields = entryType.getRequiredFields().stream()
.map(OrFields::getFields)
.flatMap(Set::stream)
.collect(Collectors.toSet());
Set<BibField> entryTypeOptionalFields = entryType.getOptionalFields();

if (standardRequiredFields.equals(entryTypeRequiredFields) && standardOptionalFields.equals(entryTypeOptionalFields)) {
entryTypes.removeCustomOrModifiedEntryType(entryType);
return;
}
LOGGER.debug("Different standard type fields for {} and standard {}", entryType, standardType);
entryTypes.addCustomOrModifiedType(entryType);
}

public void removeCustomOrModifiedEntryType(BibEntryType entryType, BibDatabaseMode mode) {
getEntryTypes(mode).removeCustomOrModifiedEntryType(entryType);
}
Expand All @@ -81,12 +164,20 @@ public void clearAllCustomEntryTypes(BibDatabaseMode mode) {
getEntryTypes(mode).clearAllCustomEntryTypes();
}

public Collection<BibEntryType> getAllTypes(BibDatabaseMode mode) {
return getEntryTypes(mode).getAllTypes();
/**
* Checks if the given type is NOT a standard type AND customized inside the entry types manager.
* There might be also types not known to the entry types manager, which are neither standard nor customized.
*/
public boolean isCustomType(EntryType type, BibDatabaseMode mode) {
return !getEntryTypes(mode).isStandardType(type) && enrich(type, mode).isPresent();
}

public boolean isCustomType(EntryType type, BibDatabaseMode mode) {
return getAllCustomTypes(mode).stream().anyMatch(customType -> customType.getType().equals(type));
/**
* Checks if the given type is NOT a standard type AND customized inside the entry types manager.
* There might be also types not known to the entry types manager, which are neither standard nor customized.
*/
public boolean isCustomType(BibEntryType type, BibDatabaseMode mode) {
return !getEntryTypes(mode).isStandardType(type) && getEntryTypes(mode).isCustomOrModifiedType(type);
}

/**
Expand All @@ -98,26 +189,19 @@ public Optional<BibEntryType> enrich(EntryType type, BibDatabaseMode mode) {
return getEntryTypes(mode).enrich(type);
}

public boolean isDifferentCustomOrModifiedType(BibEntryType type, BibDatabaseMode mode) {
Optional<BibEntryType> currentlyStoredType = enrich(type.getType(), mode);
if (currentlyStoredType.isEmpty()) {
// new customization
return true;
} else {
// different customization
return !EntryTypeFactory.nameAndFieldsAreEqual(type, currentlyStoredType.get());
}
}

/**
* This class is used to specify entry types for either BIBTEX and BIBLATEX.
*/
private static class InternalEntryTypes {
@VisibleForTesting
static class InternalEntryTypes {
@VisibleForTesting
final Set<BibEntryType> standardTypes;

// TreeSet needs to be used here, because then, org.jabref.model.entry.BibEntryType.compareTo is used - instead of org.jabref.model.entry.BibEntryType.equals
private final SortedSet<BibEntryType> customOrModifiedType = new TreeSet<>();
private final SortedSet<BibEntryType> standardTypes;

private InternalEntryTypes(List<BibEntryType> standardTypes) {
this.standardTypes = new TreeSet<>(standardTypes);
this.standardTypes = new HashSet<>(standardTypes);
}

private List<BibEntryType> getAllCustomTypes() {
Expand Down Expand Up @@ -146,7 +230,7 @@ private Optional<BibEntryType> enrich(EntryType type) {
}
}

private Predicate<BibEntryType> typeEquals(EntryType toCompare) {
static Predicate<BibEntryType> typeEquals(EntryType toCompare) {
return item -> item.getType().equals(toCompare);
}

Expand All @@ -163,15 +247,47 @@ private void clearAllCustomEntryTypes() {
customOrModifiedType.clear();
}

/**
* Returns all types known to JabRef. This includes the standard types as well as the customized types
*/
private SortedSet<BibEntryType> getAllTypes() {
SortedSet<BibEntryType> allTypes = new TreeSet<>(customOrModifiedType);
allTypes.addAll(standardTypes);
return allTypes;
}

/**
* Returns all types which are customized (be it a variant of a standard type or a completely new type)
*/
private SortedSet<BibEntryType> getAllCustomizedTypes() {
return new TreeSet<>(customOrModifiedType);
}

private boolean isCustomOrModifiedType(BibEntryType entryType) {
return customOrModifiedType.stream()
.anyMatch(customizedType -> customizedType.equals(entryType));
boolean contains = customOrModifiedType.contains(entryType);
if (!contains) {
return false;
}
Optional<BibEntryType> standardType = getStandardType(entryType);
if (standardType.isEmpty()) {
// No standard type - and customized, then it is a custom type
return true;
}
// In case of a standard type, we need to check if the fields are different.
// The TreeSet uses compareTo and not equals, thus we need to get the stored type to do a deep comparison
return !EntryTypeFactory.nameAndFieldsAreEqual(standardType.get(), entryType);
}

private Optional<BibEntryType> getStandardType(BibEntryType entryType) {
return standardTypes.stream().filter(item -> item.getType().equals(entryType.getType())).findAny();
}

private boolean isStandardType(BibEntryType entryType) {
return getStandardType(entryType).isPresent();
}

private boolean isStandardType(EntryType entryType) {
return standardTypes.stream().anyMatch(item -> item.getType().equals(entryType));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public enum BiblatexApaField implements Field {

public static <T> Optional<BiblatexApaField> fromName(T type, String name) {
if (!(type instanceof BiblatexApaEntryType)) {
// Also returns nothing if no type is given.
// Reason: The field should also be recognized in the presence of a BiblatexApa entry type.
return Optional.empty();
}
return Arrays.stream(BiblatexApaField.values())
Expand Down
Loading
Loading