<commit_before>package org.jabref.model.entry;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;

import org.jabref.model.EntryTypes;
import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.entry.event.EntryEventSource;
import org.jabref.model.entry.event.FieldChangedEvent;
import org.jabref.model.entry.identifier.DOI;
import org.jabref.model.strings.LatexToUnicodeAdapter;
import org.jabref.model.strings.StringUtil;

import com.google.common.base.Strings;
import com.google.common.eventbus.EventBus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//Esta é a classe responsável para inserção de um novo item bibliográfico
public class BibEntry implements Cloneable {

    public static final String TYPE_HEADER = "entrytype";
    public static final String OBSOLETE_TYPE_HEADER = "bibtextype";
    //Encontrado o atributo
    public static final String KEY_FIELD = "bibtexkey";
    public static final String DEFAULT_TYPE = "misc";
    protected static final String ID_FIELD = "id";
    private static final Log LOGGER = LogFactory.getLog(BibEntry.class);
    private static final Pattern REMOVE_TRAILING_WHITESPACE = Pattern.compile("\\s+$");
    private final SharedBibEntryData sharedBibEntryData;
    /**
     * Map to store the words in every field
     */
    private final Map<String, Set<String>> fieldsAsWords = new HashMap<>();
    /**
     * Cache that stores latex free versions of fields.
     */
    private final Map<String, String> latexFreeFields = new ConcurrentHashMap<>();
    private final EventBus eventBus = new EventBus();
    private String id;
    private String type;
    private ObservableMap<String, String> fields = FXCollections.observableMap(new ConcurrentHashMap<>());
    // Search and grouping status is stored in boolean fields for quick reference:
    private boolean searchHit;
    private boolean groupHit;
    private String parsedSerialization;
    private String commentsBeforeEntry = "";
    /**
     * 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;

    /**
     * Constructs a new BibEntry. The internal ID is set to IdGenerator.next()
     */

    public BibEntry() {
        this(IdGenerator.next(), DEFAULT_TYPE);
    }

    /**
     * Constructs a new BibEntry with the given type
     *
     * @param type The type to set. May be null or empty. In that case, DEFAULT_TYPE is used.
     */
    public BibEntry(String type) {
        this(IdGenerator.next(), 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.
     */
    private BibEntry(String id, String type) {
        Objects.requireNonNull(id, "Every BibEntry must have an ID");

        this.id = id;
        setType(type);
        this.sharedBibEntryData = new SharedBibEntryData();
    }

    public Optional<FieldChange> setMonth(Month parsedMonth) {
        return setField(FieldName.MONTH, parsedMonth.getJabRefFormat());
    }

    public Optional<FieldChange> replaceKeywords(KeywordList keywordsToReplace, Optional<Keyword> newValue,
            Character keywordDelimiter) {
        KeywordList keywordList = getKeywords(keywordDelimiter);
        if (newValue.isPresent()) {
            keywordList.replaceAll(keywordsToReplace, newValue.get());
        }

        return putKeywords(keywordList, keywordDelimiter);
    }

    /**
     * Returns the text stored in the given field of the given bibtex entry
     * which belongs to the given database.
     * <p>
     * If a database is given, this function will try to resolve any string
     * references in the field-value.
     * Also, if a database is given, this function will try to find values for
     * unset fields in the entry linked by the "crossref" field, if any.
     *
     * @param field    The field to return the value of.
     * @param database maybenull
     *                 The database of the bibtex entry.
     * @return The resolved field value or null if not found.
     */
    public Optional<String> getResolvedFieldOrAlias(String field, BibDatabase database) {
        Objects.requireNonNull(this, "entry cannot be null");

        if (TYPE_HEADER.equals(field) || OBSOLETE_TYPE_HEADER.equals(field)) {
            Optional<EntryType> entryType = EntryTypes.getType(getType(), BibDatabaseMode.BIBLATEX);
            if (entryType.isPresent()) {
                return Optional.of(entryType.get().getName());
            } else {
                return Optional.of(StringUtil.capitalizeFirst(getType()));
            }
        }

        if (KEY_FIELD.equals(field)) {
            return getCiteKeyOptional();
        }

        Optional<String> result = getFieldOrAlias(field);

        // If this field is not set, and the entry has a crossref, try to look up the
        // field in the referred entry: Do not do this for the bibtex key.
        if (!result.isPresent() && (database != null)) {
            Optional<BibEntry> referred = database.getReferencedEntry(this);
            result = referred.flatMap(entry -> entry.getFieldOrAlias(field));
        }
        return result.map(resultText -> BibDatabase.getText(resultText, database));
    }

    /**
     * Returns this entry's ID.
     */
    public String getId() {
        return id;
    }

    /**
     * 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");

        String oldId = this.id;

        eventBus.post(new FieldChangedEvent(this, BibEntry.ID_FIELD, id, oldId));
        this.id = id;
        changed = true;
    }

    /**
     * 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.
     */
    @Deprecated
    public String getCiteKey() {
        return fields.get(KEY_FIELD);
    }

    //Aqui foi encontrado a parte do código que define uma BibTeX key, que é uma chave de citação
    /**
     * 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; use {@link #clearCiteKey()} to remove the cite key.
     */

    //Validação do campo BibTeX para que ele satisfaça as seguintes restrições:
    //-> Ter no mínimo 2 caracteres
    //-> Primeiro caractere ser uma letra (maiúscula ou minúscula)
    //-> Deve ser definido pelo usuário ou automaticamente

    //Função que gera uma string aleatória de 8 caracteres
    protected String geraString() {
        String fonte = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
        StringBuilder gerada = new StringBuilder();
        Random rnd = new Random();
        while (gerada.length() < 8) { // length of the random string.
            int index = (int) (rnd.nextFloat() * fonte.length());
            gerada.append(fonte.charAt(index));
        }
        String saida = gerada.toString();
        return saida;
    }

    public void setCiteKey(String newCiteKey) {

        String keyAutomatica;

        keyAutomatica = geraString();

        char chave[] = newCiteKey.toCharArray();

        //Condição para verificar se a key escolhida é válida:
        if ((newCiteKey.length() < 2) || (Character.isDigit(chave[0]))) {
            setField(KEY_FIELD, keyAutomatica); //É definida automaticamente pelo sistema
        }
        else {
            setField(KEY_FIELD, newCiteKey); //É definida pelo usuário
        }

        setField(KEY_FIELD, newCiteKey);
    }

    public Optional<String> getCiteKeyOptional() {
        return Optional.ofNullable(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(EntryType type) {
        this.setType(type.getName());
    }

    /**
     * Sets this entry's type.
     */
    public void setType(String type) {
        setType(type, EntryEventSource.LOCAL);
    }

    /**
     * Sets this entry's type.
     */
    public void setType(String type, EntryEventSource eventSource) {
        String newType;
        if (Strings.isNullOrEmpty(type)) {
            newType = DEFAULT_TYPE;
        } else {
            newType = type;
        }
        String oldType = getField(TYPE_HEADER).orElse(null);

        // 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, oldType, eventSource));
    }

    /**
     * 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 as an Optional.
     */
    public Optional<String> getField(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);
    }

    /**
     * Internal method used to get the content of a field (or its alias)
     *
     * Used by {@link #getFieldOrAlias(String)} and {@link #getFieldOrAliasLatexFree(String)}
     *
     * @param name name of the field
     * @param getFieldInterface
     *
     * @return determined field value
     */
    private Optional<String> genericGetFieldOrAlias(String name, GetFieldInterface getFieldInterface) {
        Optional<String> fieldValue = getFieldInterface.getValueForField(toLowerCase(name));

        if (fieldValue.isPresent() && !fieldValue.get().isEmpty()) {
            return fieldValue;
        }

        // No value of this field found, so look at the alias
        String aliasForField = EntryConverter.FIELD_ALIASES.get(name);

        if (aliasForField != null) {
            return getFieldInterface.getValueForField(aliasForField);
        }

        // Finally, handle dates
        if (FieldName.DATE.equals(name)) {
            Optional<Date> date = Date.parse(
                    getFieldInterface.getValueForField(FieldName.YEAR),
                    getFieldInterface.getValueForField(FieldName.MONTH),
                    getFieldInterface.getValueForField(FieldName.DAY));

            return date.map(Date::getNormalized);
        }

        if (FieldName.YEAR.equals(name) || FieldName.MONTH.equals(name) || FieldName.DAY.equals(name)) {
            Optional<String> date = getFieldInterface.getValueForField(FieldName.DATE);
            if (!date.isPresent()) {
                return Optional.empty();
            }

            Optional<Date> parsedDate = Date.parse(date.get());
            if (parsedDate.isPresent()) {
                if (FieldName.YEAR.equals(name)) {
                    return parsedDate.get().getYear().map(Object::toString);
                }
                if (FieldName.MONTH.equals(name)) {
                    return parsedDate.get().getMonth().map(Month::getJabRefFormat);
                }
                if (FieldName.DAY.equals(name)) {
                    return parsedDate.get().getDay().map(Object::toString);
                }
            } else {
                LOGGER.warn("Could not parse date " + date.get());
                return Optional.empty(); // Date field not in valid format
            }
        }
        return Optional.empty();
    }

    public Optional<DOI> getDOI() {
        return getField(FieldName.DOI).flatMap(DOI::parse);
    }

    /**
     * Return the LaTeX-free contents of the given field or its alias an an Optional
     *
     * For details see also {@link #getFieldOrAlias(String)}
     *
     * @param name the name of the field
     * @return  the stored latex-free content of the field (or its alias)
     */
    public Optional<String> getFieldOrAliasLatexFree(String name) {
        return genericGetFieldOrAlias(name, this::getLatexFreeField);
    }

    /**
     * Returns the contents of the given field or its alias as an Optional
     * <p>
     * The following aliases are considered (old bibtex <-> new biblatex) based
     * on the biblatex documentation, chapter 2.2.5:<br>
     * address        <-> location <br>
     * annote         <-> annotation <br>
     * archiveprefix  <-> eprinttype <br>
     * journal        <-> journaltitle <br>
     * key            <-> sortkey <br>
     * pdf            <-> file <br
     * primaryclass   <-> eprintclass <br>
     * school         <-> institution <br>
     * These work bidirectional. <br>
     * </p>
     *
     * <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').
     * </p>
     */
    public Optional<String> getFieldOrAlias(String name) {
        return genericGetFieldOrAlias(name, this::getField);
    }

    /**
     * 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
     * @param eventSource Source the event is sent from
     */
    public Optional<FieldChange> setField(String name, String value, EntryEventSource eventSource) {
        Objects.requireNonNull(name, "field name must not be null");
        Objects.requireNonNull(value, "field value must not be null");

        String fieldName = toLowerCase(name);

        if (value.isEmpty()) {
            return clearField(fieldName);
        }

        String oldValue = getField(fieldName).orElse(null);
        if (value.equals(oldValue)) {
            return Optional.empty();
        }

        if (BibEntry.ID_FIELD.equals(fieldName)) {
            throw new IllegalArgumentException("The field name '" + name + "' is reserved");<commit_msg>Validação do campo ano #2<commit_after>package org.jabref.model.entry;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;

import org.jabref.model.EntryTypes;
import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.entry.event.EntryEventSource;
import org.jabref.model.entry.event.FieldChangedEvent;
import org.jabref.model.entry.identifier.DOI;
import org.jabref.model.strings.LatexToUnicodeAdapter;
import org.jabref.model.strings.StringUtil;

import com.google.common.base.Strings;
import com.google.common.eventbus.EventBus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//Esta é a classe responsável para inserção de um novo item bibliográfico
public class BibEntry implements Cloneable {

    public static final String TYPE_HEADER = "entrytype";
    public static final String OBSOLETE_TYPE_HEADER = "bibtextype";
    //Encontrado o atributo
    public static final String KEY_FIELD = "bibtexkey";
    public static final String DEFAULT_TYPE = "misc";
    protected static final String ID_FIELD = "id";
    private static final Log LOGGER = LogFactory.getLog(BibEntry.class);
    private static final Pattern REMOVE_TRAILING_WHITESPACE = Pattern.compile("\\s+$");
    private final SharedBibEntryData sharedBibEntryData;
    /**
     * Map to store the words in every field
     */
    private final Map<String, Set<String>> fieldsAsWords = new HashMap<>();
    /**
     * Cache that stores latex free versions of fields.
     */
    private final Map<String, String> latexFreeFields = new ConcurrentHashMap<>();
    private final EventBus eventBus = new EventBus();
    private String id;
    private String type;
    private ObservableMap<String, String> fields = FXCollections.observableMap(new ConcurrentHashMap<>());
    // Search and grouping status is stored in boolean fields for quick reference:
    private boolean searchHit;
    private boolean groupHit;
    private String parsedSerialization;
    private String commentsBeforeEntry = "";
    /**
     * 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;

    /**
     * Constructs a new BibEntry. The internal ID is set to IdGenerator.next()
     */

    public BibEntry() {
        this(IdGenerator.next(), DEFAULT_TYPE);
    }

    /**
     * Constructs a new BibEntry with the given type
     *
     * @param type The type to set. May be null or empty. In that case, DEFAULT_TYPE is used.
     */
    public BibEntry(String type) {
        this(IdGenerator.next(), 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.
     */
    private BibEntry(String id, String type) {
        Objects.requireNonNull(id, "Every BibEntry must have an ID");

        this.id = id;
        setType(type);
        this.sharedBibEntryData = new SharedBibEntryData();
    }

    public Optional<FieldChange> setMonth(Month parsedMonth) {
        return setField(FieldName.MONTH, parsedMonth.getJabRefFormat());
    }

    public Optional<FieldChange> replaceKeywords(KeywordList keywordsToReplace, Optional<Keyword> newValue,
            Character keywordDelimiter) {
        KeywordList keywordList = getKeywords(keywordDelimiter);
        if (newValue.isPresent()) {
            keywordList.replaceAll(keywordsToReplace, newValue.get());
        }

        return putKeywords(keywordList, keywordDelimiter);
    }

    /**
     * Returns the text stored in the given field of the given bibtex entry
     * which belongs to the given database.
     * <p>
     * If a database is given, this function will try to resolve any string
     * references in the field-value.
     * Also, if a database is given, this function will try to find values for
     * unset fields in the entry linked by the "crossref" field, if any.
     *
     * @param field    The field to return the value of.
     * @param database maybenull
     *                 The database of the bibtex entry.
     * @return The resolved field value or null if not found.
     */
    public Optional<String> getResolvedFieldOrAlias(String field, BibDatabase database) {
        Objects.requireNonNull(this, "entry cannot be null");

        if (TYPE_HEADER.equals(field) || OBSOLETE_TYPE_HEADER.equals(field)) {
            Optional<EntryType> entryType = EntryTypes.getType(getType(), BibDatabaseMode.BIBLATEX);
            if (entryType.isPresent()) {
                return Optional.of(entryType.get().getName());
            } else {
                return Optional.of(StringUtil.capitalizeFirst(getType()));
            }
        }

        if (KEY_FIELD.equals(field)) {
            return getCiteKeyOptional();
        }

        Optional<String> result = getFieldOrAlias(field);

        // If this field is not set, and the entry has a crossref, try to look up the
        // field in the referred entry: Do not do this for the bibtex key.
        if (!result.isPresent() && (database != null)) {
            Optional<BibEntry> referred = database.getReferencedEntry(this);
            result = referred.flatMap(entry -> entry.getFieldOrAlias(field));
        }
        return result.map(resultText -> BibDatabase.getText(resultText, database));
    }

    /**
     * Returns this entry's ID.
     */
    public String getId() {
        return id;
    }

    /**
     * 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");

        String oldId = this.id;

        eventBus.post(new FieldChangedEvent(this, BibEntry.ID_FIELD, id, oldId));
        this.id = id;
        changed = true;
    }

    /**
     * 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.
     */
    @Deprecated
    public String getCiteKey() {
        return fields.get(KEY_FIELD);
    }

    //Aqui foi encontrado a parte do código que define uma BibTeX key, que é uma chave de citação
    /**
     * 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; use {@link #clearCiteKey()} to remove the cite key.
     */

    //Validação do campo BibTeX para que ele satisfaça as seguintes restrições:
    //-> Ter no mínimo 2 caracteres
    //-> Primeiro caractere ser uma letra (maiúscula ou minúscula)
    //-> Deve ser definido pelo usuário ou automaticamente

    //Função que gera uma string aleatória de 8 caracteres
    protected String geraString() {
        String fonte = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
        StringBuilder gerada = new StringBuilder();
        Random rnd = new Random();
        while (gerada.length() < 8) { // length of the random string.
            int index = (int) (rnd.nextFloat() * fonte.length());
            gerada.append(fonte.charAt(index));
        }
        String saida = gerada.toString();
        return saida;
    }

    //validando o campo ano de acordo com o calendario da linguagem java
    public boolean isValidYear(String dateString) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy");
        try {
            df.setLenient(false);
            df.parse(dateString);
            return true;
        } catch (ParseException e) {
            return false;
        }
    }

    public void setCiteKey(String newCiteKey) {

        String keyAutomatica;

        keyAutomatica = geraString();

        char chave[] = newCiteKey.toCharArray();

        //Condição para verificar se a key escolhida é válida:
        if ((newCiteKey.length() < 2) || (Character.isDigit(chave[0]))) {
            setField(KEY_FIELD, keyAutomatica); //É definida automaticamente pelo sistema
        }
        else {
            setField(KEY_FIELD, newCiteKey); //É definida pelo usuário
        }

        setField(KEY_FIELD, newCiteKey);
    }
    
    public Optional<String> getCiteKeyOptional() {
        return Optional.ofNullable(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(EntryType type) {
        this.setType(type.getName());
    }

    /**
     * Sets this entry's type.
     */
    public void setType(String type) {
        setType(type, EntryEventSource.LOCAL);
    }

    /**
     * Sets this entry's type.
     */
    public void setType(String type, EntryEventSource eventSource) {
        String newType;
        if (Strings.isNullOrEmpty(type)) {
            newType = DEFAULT_TYPE;
        } else {
            newType = type;
        }
        String oldType = getField(TYPE_HEADER).orElse(null);

        // 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, oldType, eventSource));
    }

    /**
     * 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 as an Optional.
     */
    public Optional<String> getField(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);
    }

    /**
     * Internal method used to get the content of a field (or its alias)
     *
     * Used by {@link #getFieldOrAlias(String)} and {@link #getFieldOrAliasLatexFree(String)}
     *
     * @param name name of the field
     * @param getFieldInterface
     *
     * @return determined field value
     */
    private Optional<String> genericGetFieldOrAlias(String name, GetFieldInterface getFieldInterface) {
        Optional<String> fieldValue = getFieldInterface.getValueForField(toLowerCase(name));

        if (fieldValue.isPresent() && !fieldValue.get().isEmpty()) {
            return fieldValue;
        }

        // No value of this field found, so look at the alias
        String aliasForField = EntryConverter.FIELD_ALIASES.get(name);

        if (aliasForField != null) {
            return getFieldInterface.getValueForField(aliasForField);
        }

        // Finally, handle dates
        if (FieldName.DATE.equals(name)) {
            Optional<Date> date = Date.parse(
                    getFieldInterface.getValueForField(FieldName.YEAR),
                    getFieldInterface.getValueForField(FieldName.MONTH),
                    getFieldInterface.getValueForField(FieldName.DAY));

            return date.map(Date::getNormalized);
        }

        if (FieldName.YEAR.equals(name) || FieldName.MONTH.equals(name) || FieldName.DAY.equals(name)) {
            Optional<String> date = getFieldInterface.getValueForField(FieldName.DATE);
            if (!date.isPresent()) {
                return Optional.empty();
            }

            Optional<Date> parsedDate = Date.parse(date.get());
            if (parsedDate.isPresent()) {
                if (FieldName.YEAR.equals(name)) {
                    return parsedDate.get().getYear().map(Object::toString);
                }
                if (FieldName.MONTH.equals(name)) {
                    return parsedDate.get().getMonth().map(Month::getJabRefFormat);
                }
                if (FieldName.DAY.equals(name)) {
                    return parsedDate.get().getDay().map(Object::toString);
                }
            } else {
                LOGGER.warn("Could not parse date " + date.get());
                return Optional.empty(); // Date field not in valid format
            }
        }
        return Optional.empty();
    }

    public Optional<DOI> getDOI() {
        return getField(FieldName.DOI).flatMap(DOI::parse);
    }

    /**
     * Return the LaTeX-free contents of the given field or its alias an an Optional
     *
     * For details see also {@link #getFieldOrAlias(String)}
     *
     * @param name the name of the field
     * @return  the stored latex-free content of the field (or its alias)
     */
    public Optional<String> getFieldOrAliasLatexFree(String name) {
        return genericGetFieldOrAlias(name, this::getLatexFreeField);
    }

    /**
     * Returns the contents of the given field or its alias as an Optional
     * <p>
     * The following aliases are considered (old bibtex <-> new biblatex) based
     * on the biblatex documentation, chapter 2.2.5:<br>
     * address        <-> location <br>
     * annote         <-> annotation <br>
     * archiveprefix  <-> eprinttype <br>
     * journal        <-> journaltitle <br>
     * key            <-> sortkey <br>
     * pdf            <-> file <br
     * primaryclass   <-> eprintclass <br>
     * school         <-> institution <br>
     * These work bidirectional. <br>
     * </p>
     *
     * <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').
     * </p>
     */
    public Optional<String> getFieldOrAlias(String name) {
        return genericGetFieldOrAlias(name, this::getField);
    }

    /**
     * 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
     * @param eventSource Source the event is sent from
     */
    public Optional<FieldChange> setField(String name, String value, EntryEventSource eventSource) {
        Objects.requireNonNull(name, "field name must not be null");
        Objects.requireNonNull(value, "field value must not be null");

        String fieldName = toLowerCase(name);

        if (value.isEmpty()) {
            return clearField(fieldName);
        }

        // Validação do campo year
        if (fieldName.equals("year") && !isValidYear(value))
        {
           return Optional.empty();
        }

        String oldValue = getField(fieldName).orElse(null);
        if (value.equals(oldValue)) {
            return Optional.empty();
        }

        if (BibEntry.ID_FIELD.equals(fieldName)) {
            throw new IllegalArgumentException("The field name '" + name + "' is reserved");
{'fraction_non_alphanumeric': 0.09659872261100755, 'fraction_numerical': 0.0007028695413012255, 'mean_word_length': 2.477577045696068, 'pattern_counts': {'":': 0, '<': 119, '<?xml version=': 0, '>': 123, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5319488', 'n_tokens_mistral': 9345, 'n_tokens_neox': 9061, 'n_words': 2818}