package net.sf.jabref.logic.importer.fileformat;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import net.sf.jabref.logic.exporter.SavePreferences;
import net.sf.jabref.logic.formatter.casechanger.LowerCaseFormatter;
import net.sf.jabref.logic.importer.ImportFormatPreferences;
import net.sf.jabref.logic.importer.ParseException;
import net.sf.jabref.logic.importer.ParserResult;
import net.sf.jabref.logic.util.OS;
import net.sf.jabref.model.bibtexkeypattern.AbstractBibtexKeyPattern;
import net.sf.jabref.model.bibtexkeypattern.DatabaseBibtexKeyPattern;
import net.sf.jabref.model.bibtexkeypattern.GlobalBibtexKeyPattern;
import net.sf.jabref.model.cleanup.FieldFormatterCleanup;
import net.sf.jabref.model.cleanup.FieldFormatterCleanups;
import net.sf.jabref.model.database.BibDatabaseMode;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.model.entry.BibtexString;
import net.sf.jabref.model.entry.EntryType;
import net.sf.jabref.model.entry.FieldName;
import net.sf.jabref.model.groups.AllEntriesGroup;
import net.sf.jabref.model.groups.ExplicitGroup;
import net.sf.jabref.model.groups.GroupHierarchyType;
import net.sf.jabref.model.groups.GroupTreeNode;
import net.sf.jabref.model.groups.RegexKeywordGroup;
import net.sf.jabref.model.groups.WordKeywordGroup;
import net.sf.jabref.model.metadata.SaveOrderConfig;
import net.sf.jabref.preferences.JabRefPreferences;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
 * Test the BibtexParser
 */
public class BibtexParserTest {

    private ImportFormatPreferences importFormatPreferences;
    private BibtexParser parser;


    @Before
    public void setUp() {
        importFormatPreferences = JabRefPreferences.getInstance().getImportFormatPreferences();
        parser = new BibtexParser(importFormatPreferences);
    }

    @SuppressWarnings("unused")
    @Test(expected = NullPointerException.class)
    public void parseWithNullThrowsNullPointerException() throws Exception {
        parser.parse(null);
    }

    @Test
    public void fromStringRecognizesEntry() throws ParseException {
        List<BibEntry> parsed = parser
                .parseEntries("@article{test,author={Ed von Test}}");

        BibEntry expected = new BibEntry();
        expected.setType("article");
        expected.setCiteKey("test");
        expected.setField("author", "Ed von Test");
        assertEquals(Collections.singletonList(expected), parsed);
    }

    @Test
    public void fromStringReturnsEmptyListFromEmptyString() throws ParseException {
        Collection<BibEntry> parsed = parser.parseEntries("");
        assertNotNull(parsed);
        assertEquals(Collections.emptyList(), parsed);
    }

    @Test
    public void fromStringReturnsEmptyListIfNoEntryRecognized() throws ParseException {
        Collection<BibEntry> parsed = parser
                .parseEntries("@@article@@{{{{{{}");
        assertNotNull(parsed);
        assertEquals(Collections.emptyList(), parsed);
    }

    @Test
    public void singleFromStringRecognizesEntry() throws ParseException {
        Optional<BibEntry> parsed = BibtexParser.singleFromString(
                "@article{canh05," + "  author = {Crowston, K. and Annabi, H.},\n" + "  title = {Title A}}\n",
                importFormatPreferences);

        BibEntry expected = new BibEntry();
        expected.setType("article");
        expected.setCiteKey("canh05");
        expected.setField("author", "Crowston, K. and Annabi, H.");
        expected.setField("title", "Title A");
        assertEquals(Optional.of(expected), parsed);
    }

    @Test
    public void singleFromStringRecognizesEntryInMultiple() throws ParseException {
        Optional<BibEntry> parsed = BibtexParser.singleFromString(
                "@article{canh05," + "  author = {Crowston, K. and Annabi, H.},\n" + "  title = {Title A}}\n"
                        + "@inProceedings{foo," + "  author={Norton Bar}}",
                importFormatPreferences);

        assertTrue(parsed.get().getCiteKeyOptional().equals(Optional.of("canh05"))
                || parsed.get().getCiteKeyOptional().equals(Optional.of("foo")));
    }

    @Test
    public void singleFromStringReturnsEmptyFromEmptyString() throws ParseException {
        Optional<BibEntry> parsed = BibtexParser.singleFromString("", importFormatPreferences);
        assertEquals(Optional.empty(), parsed);
    }

    @Test
    public void singleFromStringReturnsEmptyIfNoEntryRecognized() throws ParseException {
        Optional<BibEntry> parsed = BibtexParser.singleFromString("@@article@@{{{{{{}", importFormatPreferences);
        assertEquals(Optional.empty(), parsed);
    }

    @Test
    public void parseRecognizesEntry() throws IOException {
        ParserResult result = parser
                .parse(new StringReader("@article{test,author={Ed von Test}}"));

        List<BibEntry> parsed = result.getDatabase().getEntries();
        assertEquals(1, parsed.size());

        BibEntry e = parsed.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseQuotedEntries() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author=\"Ed von Test\"}"));

        List<BibEntry> parsed = result.getDatabase().getEntries();
        assertEquals(1, parsed.size());

        BibEntry e = parsed.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesEntryOnlyWithKey() throws IOException {

        ParserResult result = parser.parse(new StringReader("@article{test}"));

        List<BibEntry> parsed = result.getDatabase().getEntries();
        assertEquals(1, parsed.size());

        BibEntry e = parsed.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
    }

    @Test
    public void parseRecognizesEntryWithWhitespaceAtBegining() throws IOException {

        ParserResult result = parser
                .parse(new StringReader(" @article{test,author={Ed von Test}}"));

        List<BibEntry> parsed = result.getDatabase().getEntries();
        assertEquals(1, parsed.size());

        BibEntry e = parsed.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesEntryWithWhitespace() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article { test,author={Ed von Test}}"));

        List<BibEntry> parsed = result.getDatabase().getEntries();
        assertEquals(1, parsed.size());

        BibEntry e = parsed.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesEntryWithNewlines() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article\n{\ntest,author={Ed von Test}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesEntryWithUnknownType() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@unknown{test,author={Ed von Test}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("unknown", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesEntryWithVeryLongType() throws IOException {

        ParserResult result = parser.parse(
                new StringReader("@thisIsALongStringToTestMaybeItIsToLongWhoKnowsNOTme{test,author={Ed von Test}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("thisisalongstringtotestmaybeitistolongwhoknowsnotme", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesEntryInParenthesis() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article(test,author={Ed von Test})"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    /**
     * Test for [ 1594123 ] Failure to import big numbers Issue Reported by Ulf Martin. SF Bugs #503, #495 are also
     * related
     */
    @Test
    public void parseRecognizesEntryWithBigNumbers() throws IOException {

        ParserResult result = BibtexParser.parse(new StringReader("@article{canh05," + "isbn = 1234567890123456789,\n"
                + "isbn2 = {1234567890123456789},\n" + "small = 1234,\n" + "}"), importFormatPreferences);

        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();

        assertEquals("article", e.getType());
        assertEquals(Optional.of("canh05"), e.getCiteKeyOptional());
        assertEquals(Optional.of("1234567890123456789"), e.getField("isbn"));
        assertEquals(Optional.of("1234567890123456789"), e.getField("isbn2"));
        assertEquals(Optional.of("1234"), e.getField("small"));
    }

    @Test
    public void parseRecognizesBibtexKeyWithSpecialCharacters() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{te_st:with-special(characters),author={Ed von Test}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();

        assertEquals("article", e.getType());
        assertEquals(Optional.of("te_st:with-special(characters)"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesEntryWhereLastFieldIsFinishedWithComma() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author={Ed von Test},}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesEntryWithAtInField() throws IOException {
        ParserResult result = parser
                .parse(new StringReader("@article{test,author={Ed von T@st}}"));

        List<BibEntry> parsed = result.getDatabase().getEntries();

        BibEntry expected = new BibEntry("article").withField(BibEntry.KEY_FIELD, "test")
                .withField("author", "Ed von T@st");

        assertEquals(Collections.singletonList(expected), parsed);
    }

    @Test
    public void parseRecognizesEntryPrecedingComment() throws IOException {
        String comment = "@Comment{@article{myarticle,}" + OS.NEWLINE
                + "@inproceedings{blabla, title={the proceedings of bl@bl@}; }" + OS.NEWLINE + "}";
        String entryWithComment = comment + OS.NEWLINE + "@article{test,author={Ed von T@st}}";
        ParserResult result = parser.parse(new StringReader(entryWithComment));

        List<BibEntry> parsed = result.getDatabase().getEntries();

        BibEntry expected = new BibEntry("article").withField(BibEntry.KEY_FIELD, "test")
                .withField("author", "Ed von T@st");
        expected.setCommentsBeforeEntry(comment);

        assertEquals(Collections.singletonList(expected), parsed);

        assertEquals(expected.getUserComments(), parsed.get(0).getUserComments());
    }

    @Test
    public void parseRecognizesMultipleEntries() throws IOException {

        ParserResult result = BibtexParser.parse(
                new StringReader("@article{canh05," + "  author = {Crowston, K. and Annabi, H.},\n"
                        + "  title = {Title A}}\n" + "@inProceedings{foo," + "  author={Norton Bar}}"),
                importFormatPreferences);
        List<BibEntry> parsed = result.getDatabase().getEntries();

        List<BibEntry> expected = new ArrayList<>();
        BibEntry firstEntry = new BibEntry();
        firstEntry.setType("article");
        firstEntry.setCiteKey("canh05");
        firstEntry.setField("author", "Crowston, K. and Annabi, H.");
        firstEntry.setField("title", "Title A");
        expected.add(firstEntry);

        BibEntry secondEntry = new BibEntry();
        secondEntry.setType("inproceedings");
        secondEntry.setCiteKey("foo");
        secondEntry.setField("author", "Norton Bar");
        expected.add(secondEntry);

        assertEquals(expected, parsed);
    }

    @Test
    public void parseSetsParsedSerialization() throws IOException {
        String firstEntry = "@article{canh05," + "  author = {Crowston, K. and Annabi, H.}," + OS.NEWLINE
                + "  title = {Title A}}" + OS.NEWLINE;
        String secondEntry = "@inProceedings{foo," + "  author={Norton Bar}}";

        ParserResult result = parser
                .parse(new StringReader(firstEntry + secondEntry));

        for (BibEntry entry : result.getDatabase().getEntries()) {
            if (entry.getCiteKeyOptional().get().equals("canh05")) {
                assertEquals(firstEntry, entry.getParsedSerialization());
            } else {
                assertEquals(secondEntry, entry.getParsedSerialization());
            }
        }
    }

    @Test
    public void parseRecognizesMultipleEntriesOnSameLine() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{canh05}" + "@inProceedings{foo}"));
        List<BibEntry> parsed = result.getDatabase().getEntries();

        List<BibEntry> expected = new ArrayList<>();
        BibEntry firstEntry = new BibEntry();
        firstEntry.setType("article");
        firstEntry.setCiteKey("canh05");
        expected.add(firstEntry);

        BibEntry secondEntry = new BibEntry();
        secondEntry.setType("inproceedings");
        secondEntry.setCiteKey("foo");
        expected.add(secondEntry);

        assertEquals(expected, parsed);
    }

    @Test
    public void parseCombinesMultipleAuthorFields() throws IOException {

        ParserResult result = parser.parse(
                new StringReader("@article{test,author={Ed von Test},author={Second Author},author={Third Author}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test and Second Author and Third Author"), e.getField("author"));
    }

    @Test
    public void parseCombinesMultipleEditorFields() throws IOException {

        ParserResult result = parser.parse(
                new StringReader("@article{test,editor={Ed von Test},editor={Second Author},editor={Third Author}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test and Second Author and Third Author"), e.getField("editor"));
    }

    /**
     * Test for SF Bug #1269
     */
    @Test
    public void parseCombinesMultipleKeywordsFields() throws IOException {

        ParserResult result = parser.parse(
                new StringReader("@article{test,Keywords={Test},Keywords={Second Keyword},Keywords={Third Keyword}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Test, Second Keyword, Third Keyword"), e.getField("keywords"));
    }

    @Test
    public void parseRecognizesHeaderButIgnoresEncoding() throws IOException {
        ParserResult result = BibtexParser.parse(new StringReader("This file was created with JabRef 2.1 beta 2." + "\n"
                + "Encoding: Cp1252" + "\n" + "" + "\n" + "@INPROCEEDINGS{CroAnnHow05," + "\n"
                + "  author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}," + "\n"
                + "  title = {Effective work practices for floss development: A model and propositions}," + "\n"
                + "  booktitle = {Hawaii International Conference On System Sciences (HICSS)}," + "\n"
                + "  year = {2005}," + "\n" + "  owner = {oezbek}," + "\n" + "  timestamp = {2006.05.29}," + "\n"
                + "  url = {http://james.howison.name/publications.html}" + "\n" + "}))"),
                importFormatPreferences);
        assertEquals(Optional.empty(), result.getMetaData().getEncoding());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("inproceedings", e.getType());
        assertEquals(8, e.getFieldNames().size());
        assertEquals(Optional.of("CroAnnHow05"), e.getCiteKeyOptional());
        assertEquals(Optional.of("Crowston, K. and Annabi, H. and Howison, J. and Masango, C."), e.getField("author"));
        assertEquals(Optional.of("Effective work practices for floss development: A model and propositions"),
                e.getField("title"));
        assertEquals(Optional.of("Hawaii International Conference On System Sciences (HICSS)"),
                e.getField("booktitle"));
        assertEquals(Optional.of("2005"), e.getField("year"));
        assertEquals(Optional.of("oezbek"), e.getField("owner"));
        assertEquals(Optional.of("2006.05.29"), e.getField("timestamp"));
        assertEquals(Optional.of("http://james.howison.name/publications.html"), e.getField("url"));
    }

    @Test
    public void parseRecognizesFormatedEntry() throws IOException {
        ParserResult result = BibtexParser.parse(
                new StringReader("" + "@INPROCEEDINGS{CroAnnHow05," + "\n"
                        + "  author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}," + "\n"
                        + "  title = {Effective work practices for floss development: A model and propositions}," + "\n"
                        + "  booktitle = {Hawaii International Conference On System Sciences (HICSS)}," + "\n"
                        + "  year = {2005}," + "\n" + "  owner = {oezbek}," + "\n" + "  timestamp = {2006.05.29},"
                        + "\n" + "  url = {http://james.howison.name/publications.html}" + "\n" + "}))"),
                importFormatPreferences);
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("inproceedings", e.getType());
        assertEquals(8, e.getFieldNames().size());
        assertEquals(Optional.of("CroAnnHow05"), e.getCiteKeyOptional());
        assertEquals(Optional.of("Crowston, K. and Annabi, H. and Howison, J. and Masango, C."), e.getField("author"));
        assertEquals(Optional.of("Effective work practices for floss development: A model and propositions"),
                e.getField("title"));
        assertEquals(Optional.of("Hawaii International Conference On System Sciences (HICSS)"),
                e.getField("booktitle"));
        assertEquals(Optional.of("2005"), e.getField("year"));
        assertEquals(Optional.of("oezbek"), e.getField("owner"));
        assertEquals(Optional.of("2006.05.29"), e.getField("timestamp"));
        assertEquals(Optional.of("http://james.howison.name/publications.html"), e.getField("url"));
    }

    @Test
    public void parseRecognizesFieldValuesInQuotationMarks() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author=\"Ed von Test\"}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseRecognizesNumbersWithoutBracketsOrQuotationMarks() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,year = 2005}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("2005"), e.getField("year"));
    }

    @Test
    public void parseRecognizesUppercaseFields() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,AUTHOR={Ed von Test}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    /**
     * Test for SF Bug #806
     */
    @Test
    public void parseRecognizesAbsoluteFile() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,file = {D:\\Documents\\literature\\Tansel-PRL2006.pdf}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("D:\\Documents\\literature\\Tansel-PRL2006.pdf"), e.getField("file"));
    }

    /**
     * Test for SF Bug #48
     */
    @Test
    public void parseRecognizesDateFieldWithConcatenation() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,date = {1-4~} # nov}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("1-4~#nov#"), e.getField("date"));
    }

    @Test
    public void parseReturnsEmptyListIfNoEntryRecognized() throws IOException {
        ParserResult result = BibtexParser.parse(
                new StringReader("  author = {Crowston, K. and Annabi, H. and Howison, J. and Masango, C.}," + "\n"
                        + "  title = {Effective work practices for floss development: A model and propositions}," + "\n"
                        + "  booktitle = {Hawaii International Conference On System Sciences (HICSS)}," + "\n"
                        + "  year = {2005}," + "\n" + "  owner = {oezbek}," + "\n" + "  timestamp = {2006.05.29},"
                        + "\n" + "  url = {http://james.howison.name/publications.html}" + "\n" + "}))"),
                importFormatPreferences);
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(0, c.size());
    }

    @Test
    public void parseReturnsEmptyListIfNoEntryExistent() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("This was created with JabRef 2.1 beta 2." + "\n" + "Encoding: Cp1252" + "\n"));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(0, c.size());
    }

    @Test
    public void parseRecognizesDuplicateBibtexKeys() throws IOException {

        ParserResult result = BibtexParser
                .parse(new StringReader("@article{canh05," + "  author = {Crowston, K. and Annabi, H.},\n"
                        + "  title = {Title A}}\n" + "@inProceedings{canh05," + "  author={Norton Bar}}"),
                        importFormatPreferences);

        List<String> duplicateKeys = result.getDuplicateKeys();
        assertEquals(1, duplicateKeys.size());
        assertEquals("canh05", duplicateKeys.get(0));
    }

    @Test
    public void parseWarnsAboutEntryWithoutBibtexKey() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{,author={Ed von Test}}"));

        assertTrue(result.hasWarnings());

        Collection<BibEntry> c = result.getDatabase().getEntries();

        BibEntry e = new BibEntry();
        e.setField("author", "Ed von Test");
        e.setType("article");

        assertEquals(Collections.singletonList(e), c);
    }

    @Test
    public void parseIgnoresAndWarnsAboutEntryWithUnmatchedOpenBracket() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author={author missing bracket}"));

        assertTrue(result.hasWarnings());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(0, c.size());
    }

    /**
     * Test for SF bug 482
     */
    @Test
    public void parseAddsEscapedOpenBracketToFieldValue() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,review={escaped \\{ bracket}}"));

        assertFalse(result.hasWarnings());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(Optional.of("escaped \\{ bracket"), e.getField("review"));
    }

    @Test
    public void parseAddsEscapedClosingBracketToFieldValue() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,review={escaped \\} bracket}}"));

        assertFalse(result.hasWarnings());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(Optional.of("escaped \\} bracket"), e.getField("review"));
    }

    @Test
    public void parseIgnoresAndWarnsAboutEntryWithUnmatchedOpenBracketInQuotationMarks() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author=\"author {missing bracket\"}"));

        assertTrue(result.hasWarnings());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(0, c.size());
    }

    @Test
    public void parseIgnoresArbitraryContentAfterEntry() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author={author bracket }}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals("Size should be one, but was " + c.size(), 1, c.size());
        assertEquals("Epilog should be preserved", "}", result.getDatabase().getEpilog());
    }

    @Test
    public void parseWarnsAboutUnmatchedContentInEntryWithoutComma() throws IOException {
        ParserResult result = BibtexParser.parse(new StringReader("@article{test,author={author bracket } too much}"),
                importFormatPreferences);

        List<BibEntry> entries = result.getDatabase().getEntries();
        assertEquals(Optional.of("author bracket #too##much#"), entries.get(0).getField("author"));
    }

    @Test
    public void parseWarnsAboutUnmatchedContentInEntry() throws IOException {
        ParserResult result = BibtexParser.parse(new StringReader("@article{test,author={author bracket }, too much}"),
                importFormatPreferences);

        assertTrue("There should be warnings", result.hasWarnings());

        List<BibEntry> entries = result.getDatabase().getEntries();
        assertEquals("Size should be zero, but was " + entries.size(), 0, entries.size());
    }

    @Test
    public void parseAcceptsEntryWithAtSymbolInBrackets() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author={author @ good}}"));

        List<BibEntry> entries = result.getDatabase().getEntries();

        assertEquals(1, entries.size());
        assertEquals(Optional.of("author @ good"), entries.get(0).getField("author"));
    }

    @Test
    public void parseRecognizesEntryWithAtSymbolInQuotationMarks() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author=\"author @ good\"}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("author @ good"), e.getField("author"));
    }

    @Test
    public void parseRecognizesFieldsWithBracketsEnclosedInQuotationMarks() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author=\"Test {Ed {von} Test}\"}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Test {Ed {von} Test}"), e.getField("author"));
    }

    @Test
    public void parseRecognizesFieldsWithEscapedQuotationMarks() throws IOException {

        // Quotes in fields of the form key = "value" have to be escaped by putting them into braces
        ParserResult result = parser
                .parse(new StringReader("@article{test,author=\"Test {\" Test}\"}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Test {\" Test}"), e.getField("author"));
    }

    @Test
    public void parseIgnoresAndWarnsAboutEntryWithFieldsThatAreNotSeperatedByComma() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author={Ed von Test} year=2005}"));

        assertTrue(result.hasWarnings());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(0, c.size());
    }

    @Test
    public void parseIgnoresAndWarnsAboutCorruptedEntryButRecognizeOthers() throws IOException {

        ParserResult result = BibtexParser.parse(
                new StringReader(
                        "@article{test,author={author missing bracket}" + "@article{test,author={Ed von Test}}"),
                importFormatPreferences);

        assertTrue(result.hasWarnings());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    /**
     * Test for SF Bug #1283
     */
    @Test
    public void parseRecognizesMonthFieldsWithFollowingComma() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author={Ed von Test},month={8,}},"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(3, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
        assertEquals(Optional.of("8,"), e.getField("month"));
    }

    @Test
    public void parseRecognizesPreamble() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@preamble{some text and \\latex}"));
        assertEquals(Optional.of("some text and \\latex"), result.getDatabase().getPreamble());
    }

    @Test
    public void parseRecognizesUppercasePreamble() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@PREAMBLE{some text and \\latex}"));
        assertEquals(Optional.of("some text and \\latex"), result.getDatabase().getPreamble());
    }

    @Test
    public void parseRecognizesPreambleWithWhitespace() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@preamble {some text and \\latex}"));
        assertEquals(Optional.of("some text and \\latex"), result.getDatabase().getPreamble());
    }

    @Test
    public void parseRecognizesPreambleInParenthesis() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@preamble(some text and \\latex)"));
        assertEquals(Optional.of("some text and \\latex"), result.getDatabase().getPreamble());
    }

    @Test
    public void parseRecognizesPreambleWithConcatenation() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@preamble{\"some text\" # \"and \\latex\"}"));
        assertEquals(Optional.of("\"some text\" # \"and \\latex\""), result.getDatabase().getPreamble());
    }

    @Test
    public void parseRecognizesString() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@string{bourdieu = {Bourdieu, Pierre}}"));
        assertEquals(1, result.getDatabase().getStringCount());

        BibtexString s = result.getDatabase().getStringValues().iterator().next();
        assertEquals("bourdieu", s.getName());
        assertEquals("Bourdieu, Pierre", s.getContent());
    }

    @Test
    public void parseSavesOneNewlineAfterStringInParsedSerialization() throws IOException {

        String string = "@string{bourdieu = {Bourdieu, Pierre}}" + OS.NEWLINE;
        ParserResult result = parser
                .parse(new StringReader(string + OS.NEWLINE + OS.NEWLINE));
        assertEquals(1, result.getDatabase().getStringCount());

        BibtexString s = result.getDatabase().getStringValues().iterator().next();
        assertEquals(string, s.getParsedSerialization());
    }

    @Test
    public void parseRecognizesStringWithWhitespace() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@string {bourdieu = {Bourdieu, Pierre}}"));
        assertEquals(1, result.getDatabase().getStringCount());

        BibtexString s = result.getDatabase().getStringValues().iterator().next();
        assertEquals("bourdieu", s.getName());
        assertEquals("Bourdieu, Pierre", s.getContent());
    }

    @Test
    public void parseRecognizesStringInParenthesis() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@string(bourdieu = {Bourdieu, Pierre})"));
        assertEquals(1, result.getDatabase().getStringCount());

        BibtexString s = result.getDatabase().getStringValues().iterator().next();
        assertEquals("bourdieu", s.getName());
        assertEquals("Bourdieu, Pierre", s.getContent());
    }

    @Test
    public void parseRecognizesMultipleStrings() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@string{bourdieu = {Bourdieu, Pierre}}" + "@string{adieu = {Adieu, Pierre}}"));

        assertEquals(2, result.getDatabase().getStringCount());
        Iterator<BibtexString> iterator = result.getDatabase().getStringValues().iterator();
        BibtexString s = iterator.next();
        BibtexString t = iterator.next();

        // Sort them because we can't be sure about the order
        if (s.getName().equals("adieu")) {
            BibtexString tmp = s;
            s = t;
            t = tmp;
        }

        assertEquals("bourdieu", s.getName());
        assertEquals("Bourdieu, Pierre", s.getContent());
        assertEquals("adieu", t.getName());
        assertEquals("Adieu, Pierre", t.getContent());
    }

    @Test
    public void parseRecognizesStringAndEntry() throws IOException {

        ParserResult result = BibtexParser.parse(
                new StringReader("" + "@string{bourdieu = {Bourdieu, Pierre}}"
                        + "@book{bourdieu-2002-questions-sociologie, " + "	Address = {Paris}," + "	Author = bourdieu,"
                        + "	Isbn = 2707318256," + "	Publisher = {Minuit}," + "	Title = {Questions de sociologie},"
                        + "	Year = 2002" + "}"),
                importFormatPreferences);

        assertEquals(1, result.getDatabase().getStringCount());
        BibtexString s = result.getDatabase().getStringValues().iterator().next();
        assertEquals("bourdieu", s.getName());
        assertEquals("Bourdieu, Pierre", s.getContent());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();

        assertEquals("book", e.getType());
        assertEquals(Optional.of("bourdieu-2002-questions-sociologie"), e.getCiteKeyOptional());
        assertEquals(Optional.of("Paris"), e.getField("address"));
        assertEquals(Optional.of("#bourdieu#"), e.getField("author"));
        assertEquals(Optional.of("2707318256"), e.getField("isbn"));
        assertEquals(Optional.of("Minuit"), e.getField("publisher"));
        assertEquals(Optional.of("Questions de sociologie"), e.getField("title"));
        assertEquals(Optional.of("2002"), e.getField("year"));
    }

    @Test
    public void parseWarnsAboutStringsWithSameNameAndOnlyKeepsOne() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@string{bourdieu = {Bourdieu, Pierre}}" + "@string{bourdieu = {Other}}"));
        assertTrue(result.hasWarnings());
        assertEquals(1, result.getDatabase().getStringCount());
    }

    @Test
    public void parseIgnoresComments() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@comment{some text and \\latex}"));
        assertEquals(0, result.getDatabase().getEntries().size());
    }

    @Test
    public void parseIgnoresUpercaseComments() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@COMMENT{some text and \\latex}"));
        assertEquals(0, result.getDatabase().getEntries().size());
    }

    @Test
    public void parseIgnoresCommentsBeforeEntry() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@comment{some text and \\latex}" + "@article{test,author={Ed von Test}}"));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseIgnoresCommentsAfterEntry() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author={Ed von Test}}" + "@comment{some text and \\latex}"));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseIgnoresText() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("comment{some text and \\latex"));
        assertEquals(0, result.getDatabase().getEntries().size());
    }

    @Test
    public void parseIgnoresTextBeforeEntry() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("comment{some text and \\latex" + "@article{test,author={Ed von Test}}"));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseIgnoresTextAfterEntry() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author={Ed von Test}}" + "comment{some text and \\latex"));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(2, e.getFieldNames().size());
        assertEquals(Optional.of("Ed von Test"), e.getField("author"));
    }

    @Test
    public void parseConvertsNewlineToSpace() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,a = {a\nb}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();
        assertEquals(Optional.of("a b"), e.getField("a"));
    }

    @Test
    public void parseConvertsMultipleNewlinesToSpace() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,a = {a\n\nb}," + "b = {a\n \nb}," + "c = {a \n \n b}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();
        assertEquals(Optional.of("a b"), e.getField("a"));
        assertEquals(Optional.of("a b"), e.getField("b"));
        assertEquals(Optional.of("a b"), e.getField("c"));
    }

    @Test
    public void parseConvertsTabToSpace() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,a = {a\tb}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();
        assertEquals(Optional.of("a b"), e.getField("a"));
    }

    @Test
    public void parseConvertsMultipleTabsToSpace() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,a = {a\t\tb}," + "b = {a\t \tb}," + "c = {a \t \t b}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();
        assertEquals(Optional.of("a b"), e.getField("a"));
        assertEquals(Optional.of("a b"), e.getField("b"));
        assertEquals(Optional.of("a b"), e.getField("c"));
    }

    /**
     * Test for [2022983]
     *
     * @author Uwe Kuehn
     * @author Andrei Haralevich
     */
    @Test
    public void parsePreservesMultipleSpacesInFileField() throws IOException {
        ParserResult result = parser
                .parse(new StringReader("@article{canh05,file = {ups  sala}}"));

        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();
        assertEquals(Optional.of("ups  sala"), e.getField("file"));
    }

    @Test
    public void parsePreservesTabsInAbstractField() throws IOException {
        ParserResult result = BibtexParser.parse(new StringReader("@article{canh05,abstract = {ups  \tsala}}"),
                importFormatPreferences);
        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();
        assertEquals(Optional.of("ups  \tsala"), e.getField(FieldName.ABSTRACT));
    }

    @Test
    public void parsePreservesNewlineInAbstractField() throws IOException {
        ParserResult result = BibtexParser.parse(new StringReader("@article{canh05,abstract = {ups \nsala}}"),
                importFormatPreferences);

        Collection<BibEntry> c = result.getDatabase().getEntries();
        BibEntry e = c.iterator().next();
        assertEquals(Optional.of("ups " + OS.NEWLINE + "sala"), e.getField(FieldName.ABSTRACT));
    }

    /**
     * Test for #650
     */
    @Test
    public void parseHandlesAccentsCorrectly() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@article{test,author = {H\'{e}lne Fiaux}}"));
        assertFalse(result.hasWarnings());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(Optional.of("H\'{e}lne Fiaux"), e.getField("author"));
    }

    /**
     * Test for #669
     */
    @Test
    public void parsePreambleAndEntryWithoutNewLine() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("@preamble{some text and \\latex}@article{test,author = {H\'{e}lne Fiaux}}"));
        assertFalse(result.hasWarnings());

        assertEquals(Optional.of("some text and \\latex"), result.getDatabase().getPreamble());

        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals("article", e.getType());
        assertEquals(Optional.of("test"), e.getCiteKeyOptional());
        assertEquals(Optional.of("H\'{e}lne Fiaux"), e.getField("author"));
    }

    /**
     * Test for #669
     */
    @Test
    public void parseFileHeaderAndPreambleWithoutNewLine() throws IOException {

        ParserResult result = parser
                .parse(new StringReader("% Encoding: US-ASCII@preamble{some text and \\latex}"));
        assertFalse(result.hasWarnings());

        assertEquals(Optional.of("some text and \\latex"), result.getDatabase().getPreamble());
    }

    @Test
    public void parseSavesEntryInParsedSerialization() throws IOException {
        String testEntry = "@article{test,author={Ed von Test}}";
        ParserResult result = parser.parse(new StringReader(testEntry));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals(testEntry, e.getParsedSerialization());
    }

    @Test
    public void parseSavesOneNewlineAfterEntryInParsedSerialization() throws IOException {
        String testEntry = "@article{test,author={Ed von Test}}";
        ParserResult result = parser
                .parse(new StringReader(testEntry + OS.NEWLINE + OS.NEWLINE));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals(testEntry + OS.NEWLINE, e.getParsedSerialization());
    }

    @Test
    public void parseSavesNewlinesBeforeEntryInParsedSerialization() throws IOException {
        String testEntry = "@article{test,author={Ed von Test}}";
        ParserResult result = parser
                .parse(new StringReader(OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + testEntry));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals(OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + testEntry, e.getParsedSerialization());
    }

    @Test
    public void parseRemovesEncodingLineInParsedSerialization() throws IOException {
        String testEntry = "@article{test,author={Ed von Test}}";
        ParserResult result = parser.parse(
                new StringReader(SavePreferences.ENCODING_PREFIX + OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + testEntry));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals(OS.NEWLINE + OS.NEWLINE + testEntry, e.getParsedSerialization());
    }

    @Test
    public void parseSavesNewlinesBetweenEntriesInParsedSerialization() throws IOException {
        String testEntryOne = "@article{test1,author={Ed von Test}}";
        String testEntryTwo = "@article{test2,author={Ed von Test}}";
        ParserResult result = parser
                .parse(new StringReader(testEntryOne + OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + testEntryTwo));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(2, c.size());

        Iterator<BibEntry> i = c.iterator();
        BibEntry a = i.next();
        BibEntry b = i.next();

        // Sort them because we can't be sure about the order
        if (a.getCiteKeyOptional().equals(Optional.of("test2"))) {
            BibEntry tmp = a;
            a = b;
            b = tmp;
        }

        assertEquals(testEntryOne + OS.NEWLINE, a.getParsedSerialization());
        assertEquals(OS.NEWLINE + OS.NEWLINE + testEntryTwo, b.getParsedSerialization());
    }

    @Test
    public void parseIgnoresWhitespaceInEpilogue() throws IOException {
        ParserResult result = parser.parse(new StringReader("   " + OS.NEWLINE));

        assertEquals("", result.getDatabase().getEpilog());
    }

    @Test
    public void parseIgnoresWhitespaceInEpilogueAfterEntry() throws IOException {
        String testEntry = "@article{test,author={Ed von Test}}";
        ParserResult result = parser
                .parse(new StringReader(testEntry + OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + "  " + OS.NEWLINE));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals(testEntry + OS.NEWLINE, e.getParsedSerialization());
        assertEquals("", result.getDatabase().getEpilog());
    }

    @Test
    public void parseTrimsWhitespaceInEpilogueAfterEntry() throws IOException {
        String testEntry = "@article{test,author={Ed von Test}}";
        ParserResult result = parser
                .parse(new StringReader(testEntry + OS.NEWLINE + OS.NEWLINE + OS.NEWLINE + " epilogue " + OS.NEWLINE));
        Collection<BibEntry> c = result.getDatabase().getEntries();
        assertEquals(1, c.size());

        BibEntry e = c.iterator().next();
        assertEquals(testEntry + OS.NEWLINE, e.getParsedSerialization());
        assertEquals("epilogue", result.getDatabase().getEpilog());
    }

    @Test
    public void parseRecognizesSaveActionsAfterEntry() throws IOException {
        BibtexParser parser = this.parser;

        ParserResult parserResult = parser.parse(
                new StringReader("@InProceedings{6055279,\n" + "  Title                    = {Educational session 1},\n"
                        + "  Booktitle                = {Custom Integrated Circuits Conference (CICC), 2011 IEEE},\n"
                        + "  Year                     = {2011},\n" + "  Month                    = {Sept},\n"
                        + "  Pages                    = {1-7},\n"
                        + "  Abstract                 = {Start of the above-titled section of the conference proceedings record.},\n"
                        + "  DOI                      = {10.1109/CICC.2011.6055279},\n"
                        + "  ISSN                     = {0886-5930}\n" + "}\n" + "\n"
                        + "@comment{jabref-meta: saveActions:enabled;title[lower_case]}"));

        FieldFormatterCleanups saveActions = parserResult.getMetaData().getSaveActions().get();

        assertTrue(saveActions.isEnabled());
        assertEquals(Collections.singletonList(new FieldFormatterCleanup("title", new LowerCaseFormatter())),
                saveActions.getConfiguredActions());
    }

    @Test
    public void integrationTestSaveActions() throws IOException {
        BibtexParser parser = this.parser;

        ParserResult parserResult = parser
                .parse(new StringReader("@comment{jabref-meta: saveActions:enabled;title[lower_case]}"));
        FieldFormatterCleanups saveActions = parserResult.getMetaData().getSaveActions().get();

        assertTrue(saveActions.isEnabled());
        assertEquals(Collections.singletonList(new FieldFormatterCleanup("title", new LowerCaseFormatter())),
                saveActions.getConfiguredActions());
    }

    @Test
    public void integrationTestCustomEntryType() throws IOException {
        ParserResult result = parser.parse(
                new StringReader("@comment{jabref-entrytype: Lecturenotes: req[author;title] opt[language;url]}"));

        Map<String, EntryType> customEntryTypes = result.getEntryTypes();

        assertEquals(1, customEntryTypes.size());
        assertEquals("Lecturenotes", customEntryTypes.keySet().toArray()[0]);
        EntryType entryType = customEntryTypes.get("Lecturenotes");
        assertEquals("Lecturenotes", entryType.getName());
        assertEquals(Arrays.asList("author", "title"), entryType.getRequiredFields());
        assertEquals(Arrays.asList("language", "url"), entryType.getOptionalFields());
    }

    @Test
    public void integrationTestSaveOrderConfig() throws IOException {
        ParserResult result = BibtexParser.parse(
                new StringReader(
                        "@Comment{jabref-meta: saveOrderConfig:specified;author;false;year;true;abstract;false;}"),
                importFormatPreferences);

        Optional<SaveOrderConfig> saveOrderConfig = result.getMetaData().getSaveOrderConfig();

        assertEquals(new SaveOrderConfig(false, new SaveOrderConfig.SortCriterion("author", false),
                new SaveOrderConfig.SortCriterion("year", true), new SaveOrderConfig.SortCriterion("abstract", false)),
                saveOrderConfig.get());
    }

    @Test
    public void integrationTestCustomKeyPattern() throws IOException {
        ParserResult result = BibtexParser
                .parse(new StringReader("@comment{jabref-meta: keypattern_article:articleTest;}" + OS.NEWLINE
                        + "@comment{jabref-meta: keypatterndefault:test;}"), importFormatPreferences);

        GlobalBibtexKeyPattern pattern = JabRefPreferences.getInstance().getKeyPattern();
        AbstractBibtexKeyPattern bibtexKeyPattern = result.getMetaData().getCiteKeyPattern(pattern);

        AbstractBibtexKeyPattern expectedPattern = new DatabaseBibtexKeyPattern(pattern);
        expectedPattern.setDefaultValue("test");
        expectedPattern.addBibtexKeyPattern("article", "articleTest");

        assertEquals(expectedPattern, bibtexKeyPattern);
    }

    @Test
    public void integrationTestBiblatexMode() throws IOException {
        ParserResult result = parser
                .parse(new StringReader("@comment{jabref-meta: databaseType:biblatex;}"));

        Optional<BibDatabaseMode> mode = result.getMetaData().getMode();

        assertEquals(BibDatabaseMode.BIBLATEX, mode.get());
    }

    @Test
    public void integrationTestGroupTree() throws IOException, ParseException {
        ParserResult result = BibtexParser.parse(new StringReader("@comment{jabref-meta: groupsversion:3;}" + OS.NEWLINE
                + "@comment{jabref-meta: groupstree:" + OS.NEWLINE + "0 AllEntriesGroup:;" + OS.NEWLINE
                + "1 KeywordGroup:Fréchet\\;0\\;keywords\\;FrechetSpace\\;0\\;1\\;;" + OS.NEWLINE
                + "1 KeywordGroup:Invariant theory\\;0\\;keywords\\;GIT\\;0\\;0\\;;" + OS.NEWLINE
                + "1 ExplicitGroup:TestGroup\\;0\\;Key1\\;Key2\\;;" + "}"), importFormatPreferences);

        GroupTreeNode root = result.getMetaData().getGroups().get();

        assertEquals(new AllEntriesGroup("All entries"), root.getGroup());
        assertEquals(3, root.getNumberOfChildren());
        assertEquals(
                new RegexKeywordGroup("Fréchet", GroupHierarchyType.INDEPENDENT, "keywords", "FrechetSpace", false),
                root.getChildren().get(0).getGroup());
        assertEquals(
                new WordKeywordGroup("Invariant theory", GroupHierarchyType.INDEPENDENT, "keywords", "GIT", false, ',', false),
                root.getChildren().get(1).getGroup());
        assertEquals(Arrays.asList("Key1", "Key2"),
                ((ExplicitGroup) root.getChildren().get(2).getGroup()).getLegacyEntryKeys());
    }

    @Test
    public void integrationTestProtectedFlag() throws IOException {
        ParserResult result = parser
                .parse(new StringReader("@comment{jabref-meta: protectedFlag:true;}"));

        assertTrue(result.getMetaData().isProtected());
    }

    @Test
    public void integrationTestContentSelectors() throws IOException {
        ParserResult result = BibtexParser.parse(
                new StringReader("@Comment{jabref-meta: selector_status:approved;captured;received;status;}"), importFormatPreferences);

        List<String> values = new ArrayList(4);
        values.add("approved");
        values.add("captured");
        values.add("received");
        values.add("status");

        assertEquals(values, result.getMetaData().getContentSelectors().getSelectorValuesForField("status"));
    }

    @Test
    public void parseReallyUnknownType() throws Exception {
        String bibtexEntry = "@ReallyUnknownType{test," + OS.NEWLINE +
                " Comment                  = {testentry}" + OS.NEWLINE +
                "}";

        Collection<BibEntry> entries = parser.parseEntries(bibtexEntry);

        BibEntry expectedEntry = new BibEntry();
        expectedEntry.setType("Reallyunknowntype");
        expectedEntry.setCiteKey("test");
        expectedEntry.setField("comment", "testentry");

        assertEquals(Collections.singletonList(expectedEntry), entries);
    }

    @Test
    public void parseOtherTypeTest() throws Exception {
        String bibtexEntry = "@Other{test," + OS.NEWLINE +
                " Comment                  = {testentry}" + OS.NEWLINE +
                "}";

        Collection<BibEntry> entries = parser.parseEntries(bibtexEntry);

        BibEntry expectedEntry = new BibEntry();
        expectedEntry.setType("Other");
        expectedEntry.setCiteKey("test");
        expectedEntry.setField("comment", "testentry");

        assertEquals(Collections.singletonList(expectedEntry), entries);
    }


    @Test
    public void parseRecognizesDatabaseID() throws Exception {
        BibtexParser parser = this.parser;

        String expectedDatabaseID = "q1w2e3r4t5z6";

        StringBuilder sharedDatabaseFileContent = new StringBuilder()
                .append("% DBID: ").append(expectedDatabaseID)
                .append(OS.NEWLINE)
                .append("@Article{a}");

        ParserResult parserResult = parser.parse(new StringReader(sharedDatabaseFileContent.toString()));

        String actualDatabaseID = parserResult.getDatabase().getSharedDatabaseID().get();

        assertEquals(expectedDatabaseID, actualDatabaseID);
    }

    @Test
    public void parseDoesNotRecognizeDatabaseIDasUserComment() throws Exception {
        BibtexParser parser = this.parser;
        StringBuilder sharedDatabaseFileContent = new StringBuilder()
                .append("% Encoding: UTF-8").append(OS.NEWLINE)
                .append("% DBID: q1w2e3r4t5z6").append(OS.NEWLINE)
                .append("@Article{a}");

        ParserResult parserResult = parser.parse(new StringReader(sharedDatabaseFileContent.toString()));
        List<BibEntry> entries = parserResult.getDatabase().getEntries();

        assertEquals(1, entries.size());
        assertEquals("", entries.get(0).getUserComments());
    }

    @Test
    public void integrationTestFileDirectories() throws IOException {
        ParserResult result = BibtexParser.parse(
                new StringReader("@comment{jabref-meta: fileDirectory:\\\\Literature\\\\;}"
                        + "@comment{jabref-meta: fileDirectory-defaultOwner-user:D:\\\\Documents;}"),
                importFormatPreferences);

        assertEquals("\\Literature\\", result.getMetaData().getDefaultFileDirectory().get());
        assertEquals("D:\\Documents", result.getMetaData().getUserFileDirectory("defaultOwner-user").get());
    }

    @Test
    public void parseReturnsEntriesInSameOrder() throws IOException {
        ParserResult result = parser
                .parse(new StringReader("@article{a}" + OS.NEWLINE + "@article{b}" + OS.NEWLINE + "@inProceedings{c}"));

        List<BibEntry> expected = new ArrayList<>();
        BibEntry a = new BibEntry();
        a.setType("article");
        a.setCiteKey("a");
        expected.add(a);

        BibEntry b = new BibEntry();
        b.setType("article");
        b.setCiteKey("b");
        expected.add(b);

        BibEntry c = new BibEntry();
        c.setType("inproceedings");
        c.setCiteKey("c");
        expected.add(c);

        assertEquals(expected, result.getDatabase().getEntries());
    }

    @Test
    public void parsePrecedingComment() throws IOException {
        // @formatter:off
        String bibtexEntry = "% Some random comment that should stay here" + OS.NEWLINE +
                "@Article{test," + OS.NEWLINE +
                "  Author                   = {Foo Bar}," + OS.NEWLINE +
                "  Journal                  = {International Journal of Something}," + OS.NEWLINE +
                "  Note                     = {some note}," + OS.NEWLINE +
                "  Number                   = {1}" + OS.NEWLINE +
                "}";
        // @formatter:on

        // read in bibtex string
        ParserResult result = parser.parse(new StringReader(bibtexEntry));

        Collection<BibEntry> entries = result.getDatabase().getEntries();
        assertEquals(1, entries.size());

        BibEntry entry = entries.iterator().next();
        assertEquals(Optional.of("test"), entry.getCiteKeyOptional());
        assertEquals(5, entry.getFieldNames().size());
        Set<String> fields = entry.getFieldNames();
        assertTrue(fields.contains("author"));
        assertEquals(Optional.of("Foo Bar"), entry.getField("author"));
        assertEquals(bibtexEntry, entry.getParsedSerialization());
    }

    @Test
    public void parseCommentAndEntryInOneLine() throws IOException {
        // @formatter:off
        String bibtexEntry = "Some random comment that should stay here @Article{test," + OS.NEWLINE +
                "  Author                   = {Foo Bar}," + OS.NEWLINE +
                "  Journal                  = {International Journal of Something}," + OS.NEWLINE +
                "  Note                     = {some note}," + OS.NEWLINE +
                "  Number                   = {1}" + OS.NEWLINE +
                "}";
        // @formatter:on

        // read in bibtex string
        ParserResult result = parser.parse(new StringReader(bibtexEntry));

        Collection<BibEntry> entries = result.getDatabase().getEntries();
        assertEquals(1, entries.size());

        BibEntry entry = entries.iterator().next();
        assertEquals(Optional.of("test"), entry.getCiteKeyOptional());
        assertEquals(5, entry.getFieldNames().size());
        Set<String> fields = entry.getFieldNames();
        assertTrue(fields.contains("author"));
        assertEquals(Optional.of("Foo Bar"), entry.getField("author"));
        assertEquals(bibtexEntry, entry.getParsedSerialization());
    }

    @Test
    public void preserveEncodingPrefixInsideEntry() throws ParseException {
        List<BibEntry> parsed = parser
                .parseEntries("@article{test,author={" + SavePreferences.ENCODING_PREFIX + "}}");

        BibEntry expected = new BibEntry();
        expected.setType("article");
        expected.setCiteKey("test");
        expected.setField("author", SavePreferences.ENCODING_PREFIX);
        assertEquals(Collections.singletonList(expected), parsed);
    }

    @Test
    public void parseBracketedComment() throws IOException {
        String commentText = "@Comment{someComment}";
        ParserResult result = parser.parse(new StringReader(commentText));

        assertEquals(commentText, result.getDatabase().getEpilog());
    }

    @Test
    public void parseRegularCommentBeforeEntry() throws IOException {
        // @formatter:off
        String bibtexEntry = "@Comment{someComment} " + OS.NEWLINE +
                "@Article{test," + OS.NEWLINE +
                "  Author                   = {Foo Bar}," + OS.NEWLINE +
                "  Journal                  = {International Journal of Something}," + OS.NEWLINE +
                "  Note                     = {some note}," + OS.NEWLINE +
                "  Number                   = {1}" + OS.NEWLINE +
                "}";
        // @formatter:on

        ParserResult result = parser.parse(new StringReader(bibtexEntry));
        Collection<BibEntry> entries = result.getDatabase().getEntries();
        BibEntry entry = entries.iterator().next();

        assertEquals(bibtexEntry, entry.getParsedSerialization());
    }

    @Test
    public void parseCommentWithoutBrackets() throws IOException {
        String commentText = "@Comment someComment";
        ParserResult result = parser.parse(new StringReader(commentText));

        assertEquals(commentText, result.getDatabase().getEpilog());
    }

    @Test
    public void parseCommentWithoutBracketsBeforeEntry() throws IOException {
        // @formatter:off
        String bibtexEntry = "@Comment someComment  " + OS.NEWLINE +
                "@Article{test," + OS.NEWLINE +
                "  Author                   = {Foo Bar}," + OS.NEWLINE +
                "  Journal                  = {International Journal of Something}," + OS.NEWLINE +
                "  Note                     = {some note}," + OS.NEWLINE +
                "  Number                   = {1}" + OS.NEWLINE +
                "}";
        // @formatter:on

        ParserResult result = parser.parse(new StringReader(bibtexEntry));
        Collection<BibEntry> entries = result.getDatabase().getEntries();
        BibEntry entry = entries.iterator().next();

        assertEquals(bibtexEntry, entry.getParsedSerialization());
    }

    @Test
    public void parseCommentContainingEntries() throws IOException {
        // @formatter:off
        String bibtexEntry = "@Comment{@article{myarticle,}" + OS.NEWLINE +
                "@inproceedings{blabla, title={the proceedings of blabla}; }" + OS.NEWLINE +
                "} " + OS.NEWLINE +
                "@Article{test," + OS.NEWLINE +
                "  Author                   = {Foo Bar}," + OS.NEWLINE +
                "  Journal                  = {International Journal of Something}," + OS.NEWLINE +
                "  Note                     = {some note}," + OS.NEWLINE +
                "  Number                   = {1}" + OS.NEWLINE +
                "}";
        // @formatter:on

        ParserResult result = parser.parse(new StringReader(bibtexEntry));
        Collection<BibEntry> entries = result.getDatabase().getEntries();
        BibEntry entry = entries.iterator().next();

        assertEquals(bibtexEntry, entry.getParsedSerialization());
    }

    @Test
    public void parseCommentContainingEntriesAndAtSymbols() throws IOException {
        // @formatter:off
        String bibtexEntry = "@Comment{@article{myarticle,}" + OS.NEWLINE +
                "@inproceedings{blabla, title={the proceedings of bl@bl@}; }" + OS.NEWLINE +
                "} " + OS.NEWLINE +
                "@Article{test," + OS.NEWLINE +
                "  Author                   = {Foo@Bar}," + OS.NEWLINE +
                "  Journal                  = {International Journal of Something}," + OS.NEWLINE +
                "  Note                     = {some note}," + OS.NEWLINE +
                "  Number                   = {1}" + OS.NEWLINE +
                "}";
        // @formatter:on

        ParserResult result = parser.parse(new StringReader(bibtexEntry));
        Collection<BibEntry> entries = result.getDatabase().getEntries();
        BibEntry entry = entries.iterator().next();

        assertEquals(bibtexEntry, entry.getParsedSerialization());
    }

    @Test
    public void parseEmptyPreambleLeadsToEmpty() throws IOException {
        ParserResult result = parser.parse(new StringReader("@preamble{}"));
        assertFalse(result.hasWarnings());
        assertEquals(Optional.empty(), result.getDatabase().getPreamble());
    }

    @Test
    public void parseEmptyFileLeadsToPreamble() throws IOException {
        ParserResult result = parser.parse(new StringReader(""));
        assertFalse(result.hasWarnings());
        assertEquals(Optional.empty(), result.getDatabase().getPreamble());
    }
}
