de.undercouch.citeproc.TestSuiteRunner.java Source code

Java tutorial

Introduction

Here is the source code for de.undercouch.citeproc.TestSuiteRunner.java

Source

// Copyright 2013 Michel Kraemer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package de.undercouch.citeproc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.lang3.StringUtils;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;

import de.undercouch.citeproc.csl.CSLAbbreviationList;
import de.undercouch.citeproc.csl.CSLCitation;
import de.undercouch.citeproc.csl.CSLCitationItem;
import de.undercouch.citeproc.csl.CSLItemData;
import de.undercouch.citeproc.csl.CitationIDIndexPair;
import de.undercouch.citeproc.helper.json.JsonLexer;
import de.undercouch.citeproc.helper.json.JsonParser;
import de.undercouch.citeproc.output.Bibliography;
import de.undercouch.citeproc.output.Citation;
import de.undercouch.citeproc.script.ScriptRunner;
import de.undercouch.citeproc.script.ScriptRunnerException;
import de.undercouch.citeproc.script.ScriptRunnerFactory;
import de.undercouch.citeproc.script.ScriptRunnerFactory.RunnerType;

/**
 * Runs the CSL test suite (<a href="https://bitbucket.org/bdarcus/citeproc-test">https://bitbucket.org/bdarcus/citeproc-test</a>)
 * @author Michel Kraemer
 */
public class TestSuiteRunner {
    /**
     * Main method of the test runner
     * @param args the first argument can either be a compiled test file (.json)
     * to run or a directory containing compiled test files
     * @throws IOException if a file could not be loaded
     */
    public static void main(String[] args) throws IOException {
        TestSuiteRunner runner = new TestSuiteRunner();
        runner.runTests(new File(args[0]), RunnerType.AUTO);
    }

    /**
     * Runs tests
     * @param f either a compiled test file (.json) to run or a directory
     * containing compiled test files
     * @param runnerType the type of the script runner that will be used
     * to execute all JavaScript code
     * @throws IOException if a file could not be loaded
     */
    public void runTests(File f, RunnerType runnerType) throws IOException {
        ScriptRunnerFactory.setRunnerType(runnerType);
        {
            ScriptRunner sr = ScriptRunnerFactory.createRunner();
            System.out.println("Using script runner: " + sr.getName() + " " + sr.getVersion());
        }

        //find test files
        File[] testFiles;
        if (f.isDirectory()) {
            testFiles = f.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.endsWith(".json");
                }
            });
        } else {
            testFiles = new File[] { f };
        }

        AnsiConsole.systemInstall();
        try {
            long start = System.currentTimeMillis();
            int count = testFiles.length;
            int success = 0;

            ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

            //submit a job for each test file
            List<Future<Boolean>> fus = new ArrayList<Future<Boolean>>();
            for (File fi : testFiles) {
                fus.add(executor.submit(new TestCallable(fi)));
            }

            //receive results
            try {
                for (Future<Boolean> fu : fus) {
                    if (fu.get()) {
                        ++success;
                    }
                }
            } catch (Exception e) {
                //should never happen
                throw new RuntimeException(e);
            }

            executor.shutdown();

            //output total time
            long end = System.currentTimeMillis();
            double time = (end - start) / 1000.0;
            System.out.println("Successfully executed " + success + " of " + count + " tests.");
            System.out.println(String.format(Locale.ENGLISH, "Total time: %.3f secs", time));
        } finally {
            AnsiConsole.systemUninstall();
        }
    }

    /**
     * A callable that runs a single test file
     */
    private class TestCallable implements Callable<Boolean> {
        private File file;

        public TestCallable(File file) {
            this.file = file;
        }

        @Override
        public Boolean call() throws Exception {
            Exception ex;
            try {
                runTest(file);
                ex = null;
            } catch (IllegalStateException e) {
                ex = e;
            } catch (IOException e) {
                ex = e;
            }

            synchronized (TestSuiteRunner.this) {
                //output name
                String name = file.getName().substring(0, file.getName().length() - 5);
                System.out.print(name);
                for (int i = 0; i < (79 - name.length() - 9); ++i) {
                    System.out.print(" ");
                }

                //output result
                if (ex == null) {
                    System.out.println("[" + Ansi.ansi().fg(Ansi.Color.GREEN).a("SUCCESS").reset() + "]");
                    return Boolean.TRUE;
                } else {
                    System.out.println("[" + Ansi.ansi().fg(Ansi.Color.RED).a("FAILURE").reset() + "]");
                    System.err.println(ex.getMessage());
                    return Boolean.FALSE;
                }
            }
        }
    }

    /**
     * Runs a single test file
     * @param f the test file to run
     * @throws IOException if the file could not be loaded
     */
    @SuppressWarnings("unchecked")
    private static void runTest(File f) throws IOException {
        //load file
        Map<String, Object> conf = readFile(f);

        //get configuration
        String mode = (String) conf.get("mode");
        String result = (String) conf.get("result");
        String style = (String) conf.get("csl");
        Collection<Map<String, Object>> input = (Collection<Map<String, Object>>) conf.get("input");
        Collection<List<Object>> rawCitations = (Collection<List<Object>>) conf.get("citations");
        Collection<List<Map<String, Object>>> rawCitationItems = (Collection<List<Map<String, Object>>>) conf
                .get("citation_items");
        Map<String, Map<String, Object>> abbreviations = (Map<String, Map<String, Object>>) conf
                .get("abbreviations");
        Collection<List<String>> bibentries = (Collection<List<String>>) conf.get("bibentries");
        Map<String, Collection<Map<String, Object>>> rawBibsection = (Map<String, Collection<Map<String, Object>>>) conf
                .get("bibsection");

        //convert item data
        int i = 0;
        CSLItemData[] items = new CSLItemData[input.size()];
        for (Map<String, Object> m : input) {
            items[i++] = CSLItemData.fromJson(m);
        }

        //convert citations
        List<List<Object>> citations = null;
        if (rawCitations != null) {
            citations = new ArrayList<List<Object>>();
            for (List<Object> m : rawCitations) {
                List<Object> cits = new ArrayList<Object>();
                cits.add(CSLCitation.fromJson((Map<String, Object>) m.get(0)));

                Collection<List<Object>> coll1 = (Collection<List<Object>>) m.get(1);
                Collection<List<Object>> coll2 = (Collection<List<Object>>) m.get(2);
                List<CitationIDIndexPair> citsPre = new ArrayList<CitationIDIndexPair>();
                List<CitationIDIndexPair> citsPost = new ArrayList<CitationIDIndexPair>();
                for (List<Object> c1m : coll1) {
                    citsPre.add(CitationIDIndexPair.fromJson(c1m));
                }
                for (List<Object> c2m : coll2) {
                    citsPost.add(CitationIDIndexPair.fromJson(c2m));
                }

                cits.add(citsPre);
                cits.add(citsPost);
                citations.add(cits);
            }
        }

        //convert citation items
        List<List<CSLCitationItem>> citationItems = null;
        if (rawCitationItems != null) {
            citationItems = new ArrayList<List<CSLCitationItem>>();
            for (List<Map<String, Object>> l : rawCitationItems) {
                List<CSLCitationItem> cits = new ArrayList<CSLCitationItem>();
                for (Map<String, Object> m : l) {
                    cits.add(CSLCitationItem.fromJson(m));
                }
                citationItems.add(cits);
            }
        }

        //convert abbreviations
        DefaultAbbreviationProvider abbreviationProvider = new DefaultAbbreviationProvider();
        if (abbreviations != null) {
            for (Map.Entry<String, Map<String, Object>> e : abbreviations.entrySet()) {
                CSLAbbreviationList al = CSLAbbreviationList.fromJson(e.getValue());
                abbreviationProvider.add(e.getKey(), al);
            }
        }

        //convert the 'bibsection' configuration
        SelectionMode bibSectionMode = null;
        CSLItemData[] bibSection = null;
        CSLItemData[] bibSectionQuash = null;
        if (rawBibsection != null) {
            for (Map.Entry<String, Collection<Map<String, Object>>> e : rawBibsection.entrySet()) {
                CSLItemData[] r = convertBibSection(e.getValue());
                if (e.getKey().equals("quash")) {
                    bibSectionQuash = r;
                } else {
                    bibSection = r;
                    bibSectionMode = SelectionMode.fromString(e.getKey());
                }
            }
        }

        //create CSL processor
        ListItemDataProvider itemDataProvider = new ListItemDataProvider(items);
        TestSuiteCSL citeproc = new TestSuiteCSL(itemDataProvider, abbreviationProvider, style);

        //register citation items
        boolean nosort = mode.equals("bibliography-nosort");
        if (bibentries != null) {
            for (List<String> be : bibentries) {
                citeproc.registerCitationItems(be.toArray(new String[be.size()]), nosort);
            }
        } else if (citations == null) {
            citeproc.registerCitationItems(itemDataProvider.getIds(), nosort);
        }

        //set default citation items
        if (citations == null && citationItems == null) {
            citationItems = new ArrayList<List<CSLCitationItem>>();
            citationItems.add(citeproc.getRegistryReflist());
        }

        //make citations
        String citationResult = null;
        if (citationItems != null) {
            citationResult = "";
            for (List<CSLCitationItem> cits : citationItems) {
                if (citationResult.length() > 0) {
                    citationResult += "\n";
                }
                citationResult += citeproc.makeCitationCluster(cits.toArray(new CSLCitationItem[cits.size()]));
            }
        } else if (citations != null) {
            List<List<Object>> slice = citations.subList(0, citations.size() - 1);
            for (List<Object> cit : slice) {
                citeproc.makeCitation((CSLCitation) cit.get(0), (List<CitationIDIndexPair>) cit.get(1),
                        (List<CitationIDIndexPair>) cit.get(2));
            }

            List<Object> citation = citations.get(citations.size() - 1);
            List<Citation> r = citeproc.makeCitation((CSLCitation) citation.get(0),
                    (List<CitationIDIndexPair>) citation.get(1), (List<CitationIDIndexPair>) citation.get(2));

            Map<Integer, Integer> indexMap = new HashMap<Integer, Integer>();
            int pos = 0;
            for (Citation c : r) {
                indexMap.put(c.getIndex(), pos);
                ++pos;
            }

            List<String> resultCitations = new ArrayList<String>();
            for (int cpos = 0; cpos < citeproc.getCitationsByIndex().size(); ++cpos) {
                if (indexMap.containsKey(cpos)) {
                    resultCitations.add(">>[" + cpos + "] " + r.get(indexMap.get(cpos)).getText());
                } else {
                    resultCitations.add("..[" + cpos + "] " + citeproc.callProcessCitationCluster(cpos));
                }
            }
            citationResult = StringUtils.join(resultCitations, "\n");
        }

        //make bibliography
        if (mode.equals("bibliography") || mode.equals("bibliography-nosort")) {
            if (bibSection != null || bibSectionQuash != null) {
                citationResult = citeproc.makeBibliography(bibSectionMode, bibSection, bibSectionQuash)
                        .makeString();
            } else {
                citationResult = citeproc.makeBibliography().makeString();
            }
        } else if (mode.equals("bibliography-header")) {
            Bibliography p = citeproc.makeBibliography();
            citationResult = "";
            citationResult += "bibend: " + p.getBibEnd() + "\n";
            citationResult += "bibliography_errors: \n"; //TODO not implemented yet
            citationResult += "bibstart: " + p.getBibStart() + "\n";
            citationResult += "done: " + p.getDone() + "\n";
            citationResult += "entry_ids: " + StringUtils.join(p.getEntryIds(), ",") + "\n";
            citationResult += "entryspacing: " + p.getEntrySpacing() + "\n";
            citationResult += "linespacing: " + p.getLineSpacing() + "\n";
            citationResult += "maxoffset: " + p.getMaxOffset() + "\n";
            citationResult += "second-field-align: " + p.getSecondFieldAlign();
        }

        //compare result
        if (!result.equals(citationResult)) {
            throw new IllegalStateException("expected: <" + result + "> but was <" + citationResult + ">");
        }
    }

    /**
     * Reads a compiled test file (.json)
     * @param f the test file
     * @return the configuration read from the file
     * @throws IOException if the file could not be read
     */
    private static Map<String, Object> readFile(File f) throws IOException {
        FileInputStream fis = new FileInputStream(f);
        try {
            JsonLexer jsonLexer = new JsonLexer(new InputStreamReader(fis, "UTF-8"));
            JsonParser jsonParser = new JsonParser(jsonLexer);
            Map<String, Object> m = jsonParser.parseObject();

            //remove items whose value is 'false'
            Iterator<Map.Entry<String, Object>> i = m.entrySet().iterator();
            while (i.hasNext()) {
                Map.Entry<String, Object> e = i.next();
                if (e.getValue() instanceof Boolean && !((Boolean) e.getValue()).booleanValue()) {
                    i.remove();
                }
            }

            return m;
        } finally {
            fis.close();
        }
    }

    /**
     * Convert a 'bibsection' configuration to a list of item data objects
     * @param bs the configuration
     * @return the item data objects
     */
    private static CSLItemData[] convertBibSection(Collection<Map<String, Object>> bs) {
        CSLItemData[] r = new CSLItemData[bs.size()];
        int i = 0;
        for (Map<String, Object> s : bs) {
            String f = (String) s.get("field");
            Object v = s.get("value");
            if (f.equals("issued") && ((String) v).isEmpty()) {
                v = new HashMap<String, Object>();
            } else if (f.equals("categories")) {
                v = Arrays.asList(v);
            }
            Map<String, Object> m = new HashMap<String, Object>();
            m.put(f, v);
            r[i++] = CSLItemData.fromJson(m);
        }
        return r;
    }

    /**
     * A special citation processor that allows access to the internal API
     */
    private static class TestSuiteCSL extends CSL {
        public TestSuiteCSL(ItemDataProvider itemDataProvider, AbbreviationProvider abbreviationProvider,
                String style) throws IOException {
            super(itemDataProvider, abbreviationProvider, style);

            ScriptRunner sr = getScriptRunner();
            try {
                sr.eval(new StringReader("function __getCitationByIndex(engine) { "
                        + "return engine.registry.citationreg.citationByIndex; }"

                        + "function __callProcessCitationCluster(engine, cpos) { "
                        + "return engine.process_CitationCluster("
                        + "engine.registry.citationreg.citationByIndex[cpos].sortedItems); }"

                        + "function __getRefList(engine) {" + "return engine.registry.reflist; }"));
            } catch (ScriptRunnerException e) {
                throw new IOException("Could not evaluate inline scripts", e);
            }
        }

        public List<CSLCitation> getCitationsByIndex() {
            List<?> r;
            try {
                r = getScriptRunner().callMethod("__getCitationByIndex", List.class, getEngine());
            } catch (ScriptRunnerException e) {
                throw new IllegalArgumentException("Could not get registered citations", e);
            }

            List<CSLCitation> result = new ArrayList<CSLCitation>();
            for (Object o : r) {
                @SuppressWarnings("unchecked")
                Map<String, Object> m = (Map<String, Object>) o;
                result.add(CSLCitation.fromJson(m));
            }
            return result;
        }

        public String callProcessCitationCluster(int cpos) {
            try {
                return getScriptRunner().callMethod("__callProcessCitationCluster", String.class, getEngine(),
                        cpos);
            } catch (ScriptRunnerException e) {
                throw new IllegalArgumentException("Could not get registered citations", e);
            }
        }

        public List<CSLCitationItem> getRegistryReflist() {
            List<?> r;
            try {
                r = getScriptRunner().callMethod("__getRefList", List.class, getEngine());
            } catch (ScriptRunnerException e) {
                throw new IllegalArgumentException("Could not get registered citation items", e);
            }

            List<CSLCitationItem> result = new ArrayList<CSLCitationItem>();
            for (Object o : r) {
                @SuppressWarnings("unchecked")
                Map<String, Object> m = (Map<String, Object>) o;
                result.add(CSLCitationItem.fromJson(m));
            }
            return result;
        }

        public String makeCitationCluster(CSLCitationItem... citation) {
            try {
                return getScriptRunner().callMethod(getEngine(), "makeCitationCluster", String.class,
                        (Object) citation);
            } catch (ScriptRunnerException e) {
                throw new IllegalArgumentException("Could not make citation custer", e);
            }
        }
    }
}