Java tutorial
/******************************************************************************* * Copyright (C) 2011 Atlas of Living Australia * All Rights Reserved. * * The contents of this file are subject to the Mozilla Public * License Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. ******************************************************************************/ package au.org.ala.delta.key; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import org.apache.commons.lang.StringUtils; import au.org.ala.delta.DeltaContext.HeadingType; import au.org.ala.delta.Logger; import au.org.ala.delta.directives.AbstractDeltaContext; import au.org.ala.delta.directives.AbstractDirective; import au.org.ala.delta.directives.ConforDirectiveParserObserver; import au.org.ala.delta.directives.DirectiveParserObserver; import au.org.ala.delta.directives.ExcludeCharacters; import au.org.ala.delta.directives.ExcludeItems; import au.org.ala.delta.directives.IncludeCharacters; import au.org.ala.delta.directives.IncludeItems; import au.org.ala.delta.directives.validation.DirectiveException; import au.org.ala.delta.key.directives.KeyDirectiveParser; import au.org.ala.delta.key.directives.io.KeyOutputFileManager; import au.org.ala.delta.model.Attribute; import au.org.ala.delta.model.AttributeFactory; import au.org.ala.delta.model.Character; import au.org.ala.delta.model.DeltaDataSet; import au.org.ala.delta.model.Item; import au.org.ala.delta.model.MatchType; import au.org.ala.delta.model.MultiStateAttribute; import au.org.ala.delta.model.MultiStateCharacter; import au.org.ala.delta.model.Specimen; import au.org.ala.delta.model.TypeSettingMark; import au.org.ala.delta.model.TypeSettingMark.MarkPosition; import au.org.ala.delta.model.format.CharacterFormatter; import au.org.ala.delta.model.format.Formatter.AngleBracketHandlingMode; import au.org.ala.delta.model.format.Formatter.CommentStrippingMode; import au.org.ala.delta.model.format.ItemFormatter; import au.org.ala.delta.model.impl.SimpleAttributeData; import au.org.ala.delta.translation.FilteredCharacter; import au.org.ala.delta.translation.FilteredDataSet; import au.org.ala.delta.translation.FilteredItem; import au.org.ala.delta.translation.IncludeExcludeDataSetFilter; import au.org.ala.delta.translation.PrintFile; import au.org.ala.delta.util.Utils; public class Key implements DirectiveParserObserver { private static final int TABULATED_KEY_NAME_CELL_WIDTH = 27; private KeyContext _context; private boolean _inputFilesRead = false; private SimpleDateFormat _timeFormat = new SimpleDateFormat("HH:mm"); private SimpleDateFormat _dateFormat = new SimpleDateFormat("d-MMM-yyyy"); private ConforDirectiveParserObserver _nestedObserver; private CharacterFormatter _charFormatter = new CharacterFormatter(false, CommentStrippingMode.STRIP_ALL, AngleBracketHandlingMode.REMOVE, true, false); private ItemFormatter _itemFormatter = new ItemFormatter(false, CommentStrippingMode.STRIP_ALL, AngleBracketHandlingMode.REMOVE, true, false, false); private PrintStream _defaultOutputStream; /** * @param args * specifies the name of the input file to use. */ public static void main(String[] args) throws Exception { System.out.println(generateCreditsString()); File f = handleArgs(args); if (!f.exists()) { Logger.log("File %s does not exist!", f.getName()); return; } new Key(f).calculateKey(); } // The character(s) used for newline is system-dependent, so need to get it // from the // system properties private static String getNewLine() { return System.getProperty("line.separator"); } private static String generateCreditsString() { StringBuilder credits = new StringBuilder("KEY version 2.12 (Java)"); credits.append(getNewLine()); credits.append("M.J. Dallwitz and T.A. Paine"); credits.append(getNewLine()); credits.append("Java edition ported by the Atlas of Living Australia, 2011."); credits.append(getNewLine()); credits.append("CSIRO Division of Entomology, GPO Box 1700, Canberra, ACT 2601, Australia"); credits.append(getNewLine()); credits.append("Phone +61 2 6246 4075. Fax +61 2 6246 4000. Email delta@ento.csiro.au"); credits.append(getNewLine()); return credits.toString(); } private static File handleArgs(String[] args) throws Exception { String fileName; if (args.length == 0) { fileName = askForFileName(); } else { fileName = args[0]; } return new File(fileName); } private static String askForFileName() throws Exception { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.println(); System.out.print("Enter the full pathname of the directives file: "); String fileName = in.readLine(); return fileName; } public Key(File directivesFile) { _context = new KeyContext(directivesFile); _defaultOutputStream = _context.getOutputFileManager().getDefaultOutputStream(); } public Key(KeyContext context) { _context = context; _defaultOutputStream = _context.getOutputFileManager().getDefaultOutputStream(); } public List<File> getOutputFiles() { return _context.getOutputFileSelector().getOutputFiles(); } public void calculateKey() { try { File directivesFile = _context.getDirectivesFile(); _nestedObserver = new ConforDirectiveParserObserver(_context); boolean parseSuccessful = true; try { processDirectivesFile(directivesFile, _context); } catch (DirectiveException ex) { // Error message will be output by the _nestedObserver. Simply // stop // termination here. parseSuccessful = false; } catch (Exception ex) { throw new RuntimeException("Fatal error occurred while processing directives file", ex); } if (parseSuccessful) { readInputFiles(); boolean displayTabularKey = _context.getDisplayTabularKey(); boolean displayBracketedKey = _context.getDisplayBracketedKey(); if (displayTabularKey || displayBracketedKey) { // Don't bother // calculating // the // key if we // don't // want one! Specimen specimen = new Specimen(_context.getDataSet(), true, false, false, MatchType.OVERLAP); // List<Pair<Item, List<Attribute>>> keyList = new // ArrayList<Pair<Item, List<Attribute>>>(); TabularKey key = new TabularKey(); FilteredDataSet dataset = new FilteredDataSet(_context, new IncludeExcludeDataSetFilter(_context)); List<Character> includedCharacters = new ArrayList<Character>(); Iterator<FilteredCharacter> iterFilteredCharacters = dataset.filteredCharacters(); while (iterFilteredCharacters.hasNext()) { includedCharacters.add(iterFilteredCharacters.next().getCharacter()); } List<Item> includedItems = new ArrayList<Item>(); Iterator<FilteredItem> iterFilteredItems = dataset.filteredItems(); while (iterFilteredItems.hasNext()) { includedItems.add(iterFilteredItems.next().getItem()); } doCalculateKey(key, dataset, includedCharacters, includedItems, specimen, null, null); _defaultOutputStream.println("Key generation completed"); if (key.isKeyIncomplete()) { _defaultOutputStream.println("KEY INCOMPLETE. MORE INFORMATION NEEDED."); } Map<Integer, TypeSettingMark> typesettingMarksMap = _context.getTypeSettingMarks(); boolean typsettingMarksSpecified = !(typesettingMarksMap == null || typesettingMarksMap.isEmpty()); generateKeyOutput(key, includedCharacters, includedItems, _context.getDisplayTabularKey(), _context.getDisplayBracketedKey(), typsettingMarksSpecified); generateListingOutput(includedCharacters, includedItems, true); } else { generateListingOutput(null, null, false); } } _nestedObserver.finishedProcessing(); } catch (Exception ex) { ex.printStackTrace(); _defaultOutputStream.println(MessageFormat.format("FATAL ERROR: {0}", ex.getMessage().toString())); _defaultOutputStream.println("Execution terminated."); _defaultOutputStream.println("ABNORMAL TERMINATION."); } } private void readInputFiles() { if (!_inputFilesRead) { KeyUtils.loadDataset(_context); _inputFilesRead = true; } } private void doCalculateKey(TabularKey key, FilteredDataSet dataset, List<Character> includedCharacters, List<Item> includedItems, Specimen specimen, Map<Character, List<MultiStateAttribute>> confirmatoryCharacterValues, Map<Character, Double> usedCharacterCosts) { if (confirmatoryCharacterValues == null) { confirmatoryCharacterValues = new HashMap<Character, List<MultiStateAttribute>>(); } if (usedCharacterCosts == null) { usedCharacterCosts = new HashMap<Character, Double>(); } Set<Item> specimenAvailableTaxa = getSpecimenAvailableTaxa(specimen, includedItems); Set<Character> specimenAvailableCharacters = getSpecimenAvailableCharacters(specimen, includedCharacters); if (specimenAvailableTaxa.size() == 0) { return; } else if (specimenAvailableTaxa.size() == 1 || (_context.getStopAfterColumnNumber() != -1 && specimen.getUsedCharacters().size() == _context.getStopAfterColumnNumber())) { // Add a row to the table if a taxon has been identified (only 1 // taxon remains available) // if the column limit set using the STOP AFTER COLUMN directive has // been reached, add a row for each remaining taxon // with the used characters addRowsToTabularKey(key, specimen, specimenAvailableTaxa, confirmatoryCharacterValues, usedCharacterCosts); } else { // These won't be in order but that doesn't matter - best orders // stuff itself List<Integer> specimenAvailableCharacterNumbers = new ArrayList<Integer>(); for (Character ch : specimenAvailableCharacters) { specimenAvailableCharacterNumbers.add(ch.getCharacterId()); } List<Integer> specimenAvailableTaxaNumbers = new ArrayList<Integer>(); for (Item item : specimenAvailableTaxa) { specimenAvailableTaxaNumbers.add(item.getItemNumber()); } MultiStateCharacter bestCharacter; // Find preset character for this column/group, if there is one int currentColumn = specimen.getUsedCharacters().size() + 1; int currentGroup = getGroupCountForColumn(currentColumn - 1, key) + 1; int presetCharacterNumber = _context.getPresetCharacter(currentColumn, currentGroup); LinkedHashMap<Character, Double> bestMap = KeyBest.orderBest(_context.getDataSet(), _context.getCharacterCostsAsArray(), _context.getCalculatedItemAbundanceValuesAsArray(), specimenAvailableCharacterNumbers, specimenAvailableTaxaNumbers, _context.getRBase(), _context.getABase(), _context.getReuse(), _context.getVaryWt(), _context.getAllowImproperSubgroups()); // -1 indicates no preset character for the column/group if (presetCharacterNumber > 0) { Character presetCharacter = _context.getDataSet().getCharacter(presetCharacterNumber); if (checkPresetCharacter(presetCharacter, specimen, includedItems)) { _defaultOutputStream.println(MessageFormat.format("Using preset character {0},{1}:{2}", presetCharacterNumber, currentColumn, currentGroup)); bestCharacter = (MultiStateCharacter) presetCharacter; } else { throw new RuntimeException( MessageFormat.format("Character {0} is not suitable for use at column {1} group {2}", presetCharacterNumber, currentColumn, currentGroup)); } } else { List<Character> bestOrderCharacters = new ArrayList<Character>(bestMap.keySet()); if (bestOrderCharacters.isEmpty()) { if (specimen.getUsedCharacters().isEmpty()) { // No characters have been used therefore this is the // beginning of the investigation. // No suitable characters at the beginning of the // investigation is a fatal error throw new RuntimeException("No suitable characters. Execution terminated."); } else { // Key is incomplete - no characters left to distinguish // the remaining taxa, // so write out the characters which got us this far // through the investigation, and also // raise an error. addRowsToTabularKey(key, specimen, specimenAvailableTaxa, confirmatoryCharacterValues, usedCharacterCosts); // also mark the key as incomplete. key.setKeyIncomplete(true); } return; } else { // KEY only uses multi state characters bestCharacter = (MultiStateCharacter) bestOrderCharacters.get(0); } } double bestCharacterCost = _context.getCharacterCost(bestCharacter.getCharacterId()); List<ConfirmatoryCharacter> confirmatoryCharacters = null; int numberOfConfirmatoryCharacters = _context.getNumberOfConfirmatoryCharacters(); if (numberOfConfirmatoryCharacters > 0) { List<Character> bestOrderCharacters = new ArrayList<Character>(bestMap.keySet()); confirmatoryCharacters = getConfirmatoryCharacters(specimen, includedItems, bestOrderCharacters, bestCharacter, numberOfConfirmatoryCharacters); } for (int i = 0; i < bestCharacter.getNumberOfStates(); i++) { int stateNumber = i + 1; MultiStateAttribute attr = createMultiStateAttribute(bestCharacter, stateNumber); specimen.setAttributeForCharacter(bestCharacter, attr); if (confirmatoryCharacters != null && !confirmatoryCharacters.isEmpty()) { List<MultiStateAttribute> confirmatoryAttributes = new ArrayList<MultiStateAttribute>(); for (ConfirmatoryCharacter confChar : confirmatoryCharacters) { int confCharNumber = confChar.getConfirmatoryCharacterNumber(); int confStateNumber = confChar.getConfirmatoryStateNumber(stateNumber); if (confStateNumber == -1) { // No matching state in the confirmatory character. // Should only be the case when using // the main state's character eliminates all // remaining taxa. Simply ignore this state. continue; } MultiStateAttribute confAttr = createMultiStateAttribute( (MultiStateCharacter) _context.getDataSet().getCharacter(confCharNumber), confStateNumber); confirmatoryAttributes.add(confAttr); } confirmatoryCharacterValues.put(bestCharacter, confirmatoryAttributes); } usedCharacterCosts.put(bestCharacter, bestCharacterCost); doCalculateKey(key, dataset, includedCharacters, includedItems, specimen, confirmatoryCharacterValues, usedCharacterCosts); specimen.removeValueForCharacter(bestCharacter); confirmatoryCharacterValues.remove(bestCharacter); usedCharacterCosts.remove(bestCharacter); } } } private void addRowsToTabularKey(TabularKey key, Specimen specimen, Set<Item> specimenAvailableTaxa, Map<Character, List<MultiStateAttribute>> confirmatoryCharacterValues, Map<Character, Double> usedCharacterCosts) { for (Item taxon : specimenAvailableTaxa) { TabularKeyRow row = new TabularKeyRow(); row.setItem(taxon); for (Character ch : specimen.getUsedCharacters()) { // Add row to key MultiStateAttribute mainCharacterValue = (MultiStateAttribute) specimen .getAttributeForCharacter(ch); row.addAttribute(mainCharacterValue, confirmatoryCharacterValues.get(ch), usedCharacterCosts.get(ch)); // If character has not already been used in key, update its // cost using the REUSE setting to increase the // probability of reuse if (!key.isCharacterUsedInKey(ch)) { double newCost = _context.getCharacterCost(ch.getCharacterId()) / _context.getReuse(); _context.setCharacterCost(ch.getCharacterId(), newCost); } } key.addRow(row); } } private MultiStateAttribute createMultiStateAttribute(MultiStateCharacter msChar, int stateNumber) { SimpleAttributeData impl = new SimpleAttributeData(false, false); MultiStateAttribute attr = (MultiStateAttribute) AttributeFactory.newAttribute(msChar, impl); Set<Integer> presentStatesSet = new HashSet<Integer>(); presentStatesSet.add(stateNumber); attr.setPresentStates(presentStatesSet); return attr; } private int getGroupCountForColumn(int columnNumber, TabularKey key) { MultiStateAttribute currentAttribute = null; int countForCurrentAttribute = 0; int groupCount = 0; for (TabularKeyRow row : key.getRows()) { List<MultiStateAttribute> attributes = row.getMainAttributes(); if (attributes.size() >= columnNumber) { MultiStateAttribute attr = attributes.get(columnNumber - 1); if (currentAttribute == null) { currentAttribute = attr; countForCurrentAttribute = 1; } else { if (attr.equals(currentAttribute)) { countForCurrentAttribute++; } else { // Do not count groups of only one attribute if (countForCurrentAttribute > 1) { groupCount++; } countForCurrentAttribute = 0; currentAttribute = attr; countForCurrentAttribute = 1; } } } } // handle the last group in the key // Do not count groups of only one attribute if (countForCurrentAttribute > 1) { groupCount++; } return groupCount; } private boolean checkPresetCharacter(Character presetCharacter, Specimen specimen, List<Item> includedItems) { // Preset characters must be multistate if (!(presetCharacter instanceof MultiStateCharacter)) { return false; } // Used characters or ones that have been made inapplicable cannot be // used as // presets if (specimen.getInapplicableCharacters().contains(presetCharacter)) { return false; } if (specimen.getUsedCharacters().contains(presetCharacter)) { return false; } // A preset character cannot completely eliminate any of the available // taxa. Each taxon must be // available after at least one of the character states is used in the // specimen Set<Item> availableTaxa = getSpecimenAvailableTaxa(specimen, includedItems); Set<Item> availableTaxaAfterPresetUsed = new HashSet<Item>(); MultiStateCharacter msPreset = (MultiStateCharacter) presetCharacter; for (int i = 0; i < msPreset.getNumberOfStates(); i++) { int stateNumber = i + 1; MultiStateAttribute attr = createMultiStateAttribute(msPreset, stateNumber); specimen.setAttributeForCharacter(msPreset, attr); availableTaxaAfterPresetUsed.addAll(getSpecimenAvailableTaxa(specimen, includedItems)); specimen.removeValueForCharacter(msPreset); } return availableTaxaAfterPresetUsed.equals(availableTaxa); } private List<ConfirmatoryCharacter> getConfirmatoryCharacters(Specimen specimen, List<Item> includedItems, List<Character> bestCharacters, MultiStateCharacter mainCharacter, int numberOfConfirmatoryCharacters) { int foundConfirmatoryCharacters = 0; List<ConfirmatoryCharacter> confirmatoryCharacters = new ArrayList<ConfirmatoryCharacter>(); List<Set<Item>> mainCharacterStateDistributions = new ArrayList<Set<Item>>(); for (int i = 0; i < mainCharacter.getNumberOfStates(); i++) { int stateNumber = i + 1; MultiStateAttribute attr = createMultiStateAttribute(mainCharacter, stateNumber); specimen.setAttributeForCharacter(mainCharacter, attr); mainCharacterStateDistributions.add(getSpecimenAvailableTaxa(specimen, includedItems)); specimen.removeValueForCharacter(mainCharacter); } for (Character ch : bestCharacters) { MultiStateCharacter multiStateChar = (MultiStateCharacter) ch; if (!multiStateChar.equals(mainCharacter)) { List<Set<Item>> confirmatoryCharacterStateDistributions = new ArrayList<Set<Item>>(); for (int i = 0; i < multiStateChar.getNumberOfStates(); i++) { int stateNumber = i + 1; MultiStateAttribute attr = createMultiStateAttribute(multiStateChar, stateNumber); specimen.setAttributeForCharacter(multiStateChar, attr); confirmatoryCharacterStateDistributions.add(getSpecimenAvailableTaxa(specimen, includedItems)); specimen.removeValueForCharacter(multiStateChar); } if (compareStateDistributions(mainCharacterStateDistributions, confirmatoryCharacterStateDistributions)) { Map<Integer, Integer> mainToConfirmatoryStateMap = new HashMap<Integer, Integer>(); for (int i = 0; i < mainCharacterStateDistributions.size(); i++) { Set<Item> distribution = mainCharacterStateDistributions.get(i); if (!distribution.isEmpty()) { int indexInConfirmatoryDistributions = confirmatoryCharacterStateDistributions .indexOf(distribution); if (indexInConfirmatoryDistributions > -1) { mainToConfirmatoryStateMap.put(i + 1, indexInConfirmatoryDistributions + 1); } } } confirmatoryCharacters.add(new ConfirmatoryCharacter(multiStateChar.getCharacterId(), mainCharacter.getCharacterId(), mainToConfirmatoryStateMap)); foundConfirmatoryCharacters++; } if (foundConfirmatoryCharacters == numberOfConfirmatoryCharacters) { break; } } } return confirmatoryCharacters; } private boolean compareStateDistributions(List<Set<Item>> dist1, List<Set<Item>> dist2) { for (Set<Item> dist1Item : dist1) { if (dist1Item.isEmpty()) { continue; } if (Collections.frequency(dist1, dist1Item) != Collections.frequency(dist2, dist1Item)) { return false; } } return true; } private Set<Item> getSpecimenAvailableTaxa(Specimen specimen, List<Item> includedItems) { Set<Item> availableTaxa = new HashSet<Item>(includedItems); for (Item item : specimen.getTaxonDifferences().keySet()) { Set<Character> differingCharacters = specimen.getTaxonDifferences().get(item); if (!differingCharacters.isEmpty()) { availableTaxa.remove(item); } } return availableTaxa; } private Set<Character> getSpecimenAvailableCharacters(Specimen specimen, List<Character> includedCharacters) { Set<Character> availableChars = new HashSet<Character>(includedCharacters); availableChars.removeAll(specimen.getUsedCharacters()); availableChars.removeAll(specimen.getInapplicableCharacters()); return availableChars; } private void processDirectivesFile(File input, KeyContext context) throws IOException, DirectiveException { KeyDirectiveParser parser = KeyDirectiveParser.createInstance(); parser.registerObserver(this); parser.parse(input, context); } public KeyContext getContext() { return _context; } private void generateKeyOutput(TabularKey tabularKey, List<Character> includedCharacters, List<Item> includedItems, boolean outputTabularKey, boolean outputBracketedKey, boolean typesettingMarksSpecified) { PrintFile printFile = _context.getOutputFileManager().getOutputFile(); if (outputTabularKey) { generateKeyHeader(printFile, tabularKey, includedCharacters, includedItems, outputTabularKey, outputBracketedKey); printTabularKey(tabularKey, printFile); _defaultOutputStream.println("Tabular key completed"); } if (outputBracketedKey) { BracketedKey bracketedKey = KeyUtils.convertTabularKeyToBracketedKey(tabularKey); if (typesettingMarksSpecified) { PrintFile typesetFile = _context.getOutputFileManager().getTypesettingFile(); generateTypesetBracketedKey(bracketedKey, includedCharacters, includedItems, typesetFile, _context.getAddCharacterNumbers(), _context.getOutputHtml(), tabularKey.getCharactersUsedInKey().size(), tabularKey.getItemsUsedInKey().size(), tabularKey.getAverageLength(), tabularKey.getAverageCost(), tabularKey.getMaximumLength(), tabularKey.getMaximumCost()); } else { if (_context.getDisplayTabularKey()) { printFile.writeBlankLines(2, 0); } generateKeyHeader(printFile, tabularKey, includedCharacters, includedItems, outputTabularKey, outputBracketedKey); printBracketedKey(bracketedKey, _context.getAddCharacterNumbers(), printFile); } _defaultOutputStream.println("Bracketed key completed"); } } private void generateKeyHeader(PrintFile printFile, TabularKey key, List<Character> includedCharacters, List<Item> includedItems, boolean outputTabularKey, boolean outputBracketedKey) { printFile.outputLine(_context.getHeading(HeadingType.HEADING)); int outputWidth = _context.getOutputFileSelector().getOutputWidth(); // The default markrtf sets the print width to zero, so use the default // here if the print width is zero. printFile.outputLine( StringUtils.repeat("*", outputWidth > 0 ? outputWidth : KeyOutputFileManager.DEFAULT_PRINT_WIDTH)); printFile.writeBlankLines(1, 0); printFile.outputLine(generateCreditsString()); printFile.writeBlankLines(1, 0); Date currentDate = Calendar.getInstance().getTime(); printFile.outputLine(MessageFormat.format("Run at {0} on {1}", _timeFormat.format(currentDate), _dateFormat.format(currentDate))); printFile.writeBlankLines(1, 0); printFile.outputLine(MessageFormat.format("Characters - {0} in data, {1} included, {2} in key.", _context.getDataSet().getNumberOfCharacters(), includedCharacters.size(), key.getCharactersUsedInKey().size())); printFile.outputLine(MessageFormat.format("Items - {0} in data, {1} included, {2} in key.", _context.getDataSet().getMaximumNumberOfItems(), includedItems.size(), key.getNumberOfRows())); printFile.writeBlankLines(1, 0); printFile.outputLine(MessageFormat.format("RBASE = {0} ABASE = {1} REUSE = {2} VARYWT = {3}", formatDouble(_context.getRBase()), formatDouble(_context.getABase()), formatDouble(_context.getReuse()), formatDouble(_context.getVaryWt()))); printFile.outputLine(MessageFormat.format("Number of confirmatory characters = {0}", _context.getNumberOfConfirmatoryCharacters())); printFile.writeBlankLines(1, 0); printFile.outputLine(MessageFormat.format("Average length of key = {0} Average cost of key = {1}", String.format("%.1f", key.getAverageLength()), String.format("%.1f", key.getAverageCost()))); printFile.outputLine(MessageFormat.format("Maximum length of key = {0} Maximum cost of key = {1}", String.format("%.1f", key.getMaximumLength()), String.format("%.1f", key.getMaximumCost()))); printFile.writeBlankLines(1, 0); if (_context.getPresetCharacters().size() > 0) { printFile.outputLine(MessageFormat.format("Preset characters (character,column:group) {0}", KeyUtils.formatPresetCharacters(_context))); printFile.writeBlankLines(1, 0); } List<Integer> includedCharacterNumbers = new ArrayList<Integer>(); for (Character ch : includedCharacters) { includedCharacterNumbers.add(ch.getCharacterId()); } printFile.outputLine(MessageFormat.format("Characters included {0}", Utils.formatIntegersAsListOfRanges(includedCharacterNumbers))); List<Integer> includedItemNumbers = new ArrayList<Integer>(); for (Item it : includedItems) { includedItemNumbers.add(it.getItemNumber()); } printFile.outputLine(MessageFormat.format("Character reliabilities {0}", KeyUtils.formatCharacterReliabilities(_context, ",", "-"))); printFile.writeBlankLines(1, 0); printFile.outputLine(MessageFormat.format("Items included {0}", Utils.formatIntegersAsListOfRanges(includedItemNumbers))); printFile.outputLine( MessageFormat.format("Item abundances {0}", KeyUtils.formatTaxonAbunances(_context, ",", "-"))); printFile.writeBlankLines(1, 0); } // NOTE: In addition to the output lines generated here, any errors and a // list of output files are also written to the listing file. // These pieces of output are inserted by the KeyOutputFileManager. private void generateListingOutput(List<Character> includedCharacters, List<Item> includedItems, boolean outputKeyConfiguration) { PrintFile listingPrintFile = getListingPrintFile(); if (outputKeyConfiguration) { DeltaDataSet dataset = _context.getDataSet(); listingPrintFile.outputLine(MessageFormat.format("Number of characters = {0} Number of items = {1}", dataset.getNumberOfCharacters(), dataset.getMaximumNumberOfItems())); listingPrintFile.outputLine(MessageFormat.format("RBASE = {0} ABASE = {1} REUSE = {2} VARYWT = {3}", formatDouble(_context.getRBase()), formatDouble(_context.getABase()), formatDouble(_context.getReuse()), formatDouble(_context.getVaryWt()))); // TODO Number of confirmatory characers, number of preset // characters List<Integer> includedCharacterNumbers = new ArrayList<Integer>(); for (Character ch : includedCharacters) { includedCharacterNumbers.add(ch.getCharacterId()); } listingPrintFile.outputLine(MessageFormat.format("Characters included {0}", Utils.formatIntegersAsListOfRanges(includedCharacterNumbers))); listingPrintFile .outputLine(MessageFormat.format("{0} characters included.", includedCharacters.size())); listingPrintFile.outputLine(MessageFormat.format("{0} items included.", includedItems.size())); listingPrintFile.writeBlankLines(1, 0); } } // Get PrintFile for handling listing output. If the LISTING FILE directive // is not used, the PrintFile instance will point to // stdout. private PrintFile getListingPrintFile() { KeyOutputFileManager outputFileManager = _context.getOutputFileManager(); int outputWidth = outputFileManager.getOutputWidth(); int pageLength = outputFileManager.getOutputPageLength(); PrintFile listingPrintFile = outputFileManager.getKeyListingFile(); if (listingPrintFile == null) { listingPrintFile = new PrintFile(outputFileManager.getDefaultOutputStream(), outputWidth, pageLength); } else { // Only append the credits if we are not outputting to stdout. // The credits are always output to stdout when the application is // started. listingPrintFile.outputLine(generateCreditsString()); listingPrintFile.writeBlankLines(1, 0); } return listingPrintFile; } private void printTabularKey(TabularKey key, PrintFile printFile) { ItemFormatter itemFormatter = new ItemFormatter(false, CommentStrippingMode.STRIP_ALL, AngleBracketHandlingMode.REMOVE, true, false, false); // Do a first pass of the data structure to get the counts for the // number of times a taxon appears in the key, and to work out how wide // the cells need to be Map<Item, Integer> itemOccurrences = new HashMap<Item, Integer>(); int cellWidth = 0; for (TabularKeyRow row : key.getRows()) { Item it = row.getItem(); if (itemOccurrences.containsKey(it)) { int currentItemCount = itemOccurrences.get(it); itemOccurrences.put(it, currentItemCount + 1); } else { itemOccurrences.put(it, 1); } // If TRUNCATE TABULAR KEY AT directive has been used, only // traverse up to the relevant column. int columnLimit = row.getNumberOfColumns(); if (_context.getTruncateTabularKeyAtColumnNumber() != -1) { columnLimit = _context.getTruncateTabularKeyAtColumnNumber(); } for (int i = 0; i < columnLimit; i++) { int columnNumber = i + 1; for (MultiStateAttribute attr : row.getAllAttributesForColumn(columnNumber)) { int characterNumber = attr.getCharacter().getCharacterId(); int numberOfDigits = Integer.toString(characterNumber).length(); // Cell width needs to be at least as wide as the number of // digits, plus one extra character for the state value // associated with the attribute if (cellWidth < numberOfDigits + 1) { cellWidth = numberOfDigits + 1; } } } } // Highest number of item occurrences in the key int maxItemOccurrences = Collections.max(itemOccurrences.values()); // Determine the maximum allowable length for a formatted taxon name in // the tabulated key. This will be the length // of the name cell, less the length of the largest number of item // occurrences (these are printed right-justified) in the cell, with a // space between the name and the item occurrence count. int maxFormattedItemNameLength = TABULATED_KEY_NAME_CELL_WIDTH - Integer.toString(maxItemOccurrences).length() - 1; StringBuilder builder = new StringBuilder(); // Second pass - output the key for (int i = 0; i < key.getNumberOfRows(); i++) { TabularKeyRow row = key.getRowAt(i); Item it = row.getItem(); List<MultiStateAttribute> rowAttributes; List<MultiStateAttribute> previousRowAttributes = null; // If TRUNCATE TABULAR KEY AT directive has been used, only // traverse up to the relevant column. int columnLimit = row.getNumberOfColumns(); if (_context.getTruncateTabularKeyAtColumnNumber() == -1) { rowAttributes = row.getAllAttributes(); if (i > 0) { previousRowAttributes = key.getRowAt(i - 1).getAllAttributes(); } } else { columnLimit = _context.getTruncateTabularKeyAtColumnNumber(); rowAttributes = row.getAllCharacterValuesUpToColumn(columnLimit); if (i > 0) { previousRowAttributes = key.getRowAt(i - 1).getAllCharacterValuesUpToColumn(columnLimit); } } // Output the dividing line between the previous row and the current // row builder.append("+---------------------------+"); for (int j = 0; j < rowAttributes.size(); j++) { Attribute currentRowAttribute = rowAttributes.get(j); if (previousRowAttributes != null && previousRowAttributes.size() >= j + 1) { Attribute previousRowAttribute = previousRowAttributes.get(j); if (currentRowAttribute.equals(previousRowAttribute)) { builder.append(StringUtils.repeat(" ", cellWidth)); builder.append("|"); } else { builder.append(StringUtils.repeat("-", cellWidth)); builder.append("+"); } } else { builder.append(StringUtils.repeat("-", cellWidth)); builder.append("+"); } } if (previousRowAttributes != null) { int diffPrevRowAttributes = previousRowAttributes.size() - rowAttributes.size(); for (int k = 0; k < diffPrevRowAttributes; k++) { builder.append(StringUtils.repeat("-", cellWidth)); builder.append("+"); } } builder.append("\n"); // Output the item name and the number of occurences of the item in // the key, if it appears more than once builder.append("|"); String formattedItemName = itemFormatter.formatItemDescription(it); formattedItemName = StringUtils.substring(formattedItemName, 0, maxFormattedItemNameLength); builder.append(formattedItemName); int numItemOccurrences = itemOccurrences.get(it); if (numItemOccurrences > 1) { builder.append(StringUtils.repeat(" ", TABULATED_KEY_NAME_CELL_WIDTH - formattedItemName.length() - Integer.toString(numItemOccurrences).length())); builder.append(numItemOccurrences); } else { builder.append(StringUtils.repeat(" ", TABULATED_KEY_NAME_CELL_WIDTH - formattedItemName.length())); } builder.append("|"); // Output the values character values used in the. Include values // for confirmatory characters if they are present for (int j = 0; j < columnLimit; j++) { int columnNumber = j + 1; List<MultiStateAttribute> cellCharacterValues = row.getAllAttributesForColumn(columnNumber); for (int k = 0; k < cellCharacterValues.size(); k++) { MultiStateAttribute cellCharacterValue = cellCharacterValues.get(k); int characterId = cellCharacterValue.getCharacter().getCharacterId(); // Insert spaces to pad out the cell if the character id + // state // value are not as wide as the cell width builder.append( StringUtils.repeat(" ", cellWidth - (Integer.toString(characterId).length() + 1))); builder.append(characterId); // Only 1 state will be ever set - the key generation // algorithm // only sets // Individual states for characters int stateNumber = cellCharacterValue.getPresentStates().iterator().next(); // Convert state numbers to "A", "B", "C" etc builder.append((char) (64 + stateNumber)); if (cellCharacterValues.size() > 1 && k < cellCharacterValues.size() - 1) { builder.append(" "); } } builder.append("|"); } builder.append("\n"); // If this is the last row, need to print the bottom edge of the // table if (i == key.getNumberOfRows() - 1) { builder.append("+---------------------------+"); for (int l = 0; l < rowAttributes.size(); l++) { builder.append(StringUtils.repeat("-", cellWidth)); builder.append("+"); } } } printPagedTabularKey(builder.toString(), cellWidth, printFile); } // If the tabular key is to wide to fit within the page width, it needs to // be spread across multiple "pages". // The taxon name column is repeated each time. private void printPagedTabularKey(String tabularKey, int cellWidth, PrintFile printFile) { int pageWidth = _context.getOutputFileManager().getOutputWidth(); pageWidth = pageWidth == 0 ? PrintFile.DEFAULT_PRINT_WIDTH : pageWidth; // add 2 dividers to width of name. int endTaxonNamesColumn = TABULATED_KEY_NAME_CELL_WIDTH + 2; // add 1 divider for each attribute column cell. int maxAttrValuesPerPage = (pageWidth - (endTaxonNamesColumn)) / (cellWidth + 1); List<String> taxonNameColumnPieces = new ArrayList<String>(); List<List<String>> allAttributeColumnsPieces = new ArrayList<List<String>>(); int maxNumberOfAttrColumnsPieces = 0; List<String> tabularKeyLines = Arrays.asList(StringUtils.split(tabularKey, '\n')); for (String line : tabularKeyLines) { taxonNameColumnPieces.add(line.substring(0, endTaxonNamesColumn)); List<String> lineAttributeColumnsPieces = new ArrayList<String>(); int pieceStartIndex = endTaxonNamesColumn; int pieceEndIndex = Math.min(endTaxonNamesColumn + ((cellWidth + 1) * maxAttrValuesPerPage), line.length()); while (pieceStartIndex < line.length()) { String piece = line.substring(pieceStartIndex, pieceEndIndex); lineAttributeColumnsPieces.add(piece); pieceStartIndex = pieceEndIndex; pieceEndIndex = Math.min(pieceEndIndex + ((cellWidth + 1) * maxAttrValuesPerPage), line.length()); } allAttributeColumnsPieces.add(lineAttributeColumnsPieces); maxNumberOfAttrColumnsPieces = Math.max(maxNumberOfAttrColumnsPieces, lineAttributeColumnsPieces.size()); } for (int i = 0; i < maxNumberOfAttrColumnsPieces; i++) { if (i > 0) { printFile.writeBlankLines(1, 0); printFile.outputLine(_context.getHeading(HeadingType.HEADING)); printFile.outputLine(StringUtils.repeat("*", _context.getOutputFileSelector().getOutputWidth())); printFile.writeBlankLines(1, 0); } for (int j = 0; j < taxonNameColumnPieces.size(); j++) { List<String> lineAttributeColumnsPieces = allAttributeColumnsPieces.get(j); if (lineAttributeColumnsPieces.size() >= i + 1) { printFile.outputLine(taxonNameColumnPieces.get(j) + lineAttributeColumnsPieces.get(i)); } else { printFile.outputLine(taxonNameColumnPieces.get(j)); } } } } private void printBracketedKey(BracketedKey bracketedKey, boolean displayCharacterNumbers, PrintFile printFile) { int numberOfIndicies = bracketedKey.getNumberOfNodes(); // Amount that lines in the bracketed key need to be indented so that // all lines for an indent line up, given the amount that the // first line is pushed out due to the index number and back reference. // Need space for the index number and backreference, plus // one each for the open and close bracket, and the full stop, plus one // extra space int indexNumberIndent = Integer.toString(numberOfIndicies).length() * 2 + 4; // If a line wraps, // add an extra 1 // space on top of // the indent from // the index and // backreference // numbers int indexLineWrapIndent = indexNumberIndent + 1; printFile.setLineWrapIndent(indexLineWrapIndent); // allow print file to wrap lines on '.' as well as spaces List<java.lang.Character> lineWrapChars = new ArrayList<java.lang.Character>(); lineWrapChars.add(' '); lineWrapChars.add('.'); printFile.setIndentOnLineWrap(true); printFile.setTrimInput(false, false); for (BracketedKeyNode node : bracketedKey) { outputBrackedKeyNode(printFile, node, displayCharacterNumbers, indexNumberIndent, indexLineWrapIndent); printFile.writeBlankLines(1, 0); } } private void outputBrackedKeyNode(PrintFile printFile, BracketedKeyNode node, boolean displayCharacterNumbers, int indexNumberIndent, int indexLineWrapIndent) { int outputWidth = _context.getOutputFileManager().getOutputWidth(); // An index contains an "entry" for each group of attributes for (int i = 0; i < node.getNumberOfLines(); i++) { StringBuilder entryBuilder = new StringBuilder(); if (i == 0) { entryBuilder.append(node.getNodeNumber()); entryBuilder.append("(").append(node.getBackReference()).append(")").append("."); entryBuilder.append(" "); } else { entryBuilder.append(StringUtils.repeat(" ", indexNumberIndent)); } List<MultiStateAttribute> attrs = node.getAttributesForLine(i); for (int j = 0; j < attrs.size(); j++) { MultiStateAttribute attr = attrs.get(j); if (displayCharacterNumbers) { entryBuilder.append("("); entryBuilder.append(attr.getCharacter().getCharacterId()); entryBuilder.append(") "); } entryBuilder.append(_charFormatter.formatCharacterDescription(attr.getCharacter())); entryBuilder.append(" "); entryBuilder.append( _charFormatter.formatState(attr.getCharacter(), attr.getPresentStates().iterator().next())); // Don't put a space after the last attribute description if (j < attrs.size() - 1) { entryBuilder.append(" "); } } Object itemListOrIndexNumber = node.getDestinationForLine(i); if (itemListOrIndexNumber instanceof List<?>) { // Index references one or more taxa StringBuilder taxonListBuilder = new StringBuilder(); List<Item> taxa = (List<Item>) itemListOrIndexNumber; for (int k = 0; k < taxa.size(); k++) { Item taxon = taxa.get(k); String taxonDescription = _itemFormatter.formatItemDescription(taxon); if (k == 0) { printFile.outputStringPairWithPaddingCharacter(entryBuilder.toString(), " " + taxonDescription, '.'); } else { printFile.outputLine(StringUtils.repeat(" ", outputWidth - taxonDescription.length()) + taxonDescription); } } } else { // Index references another index int forwardReference = (Integer) itemListOrIndexNumber; String forwardReferenceAsString = Integer.toString(forwardReference); printFile.outputStringPairWithPaddingCharacter(entryBuilder.toString(), " " + forwardReferenceAsString, '.'); } } } private void generateTypesetBracketedKey(BracketedKey bracketedKey, List<Character> includedCharacters, List<Item> includedItems, PrintFile typesetFile, boolean displayCharacterNumbers, boolean outputHtml, int numCharactersUsedInKey, int numTaxaUsedInKey, double avgLenKey, double avgCostKey, double maxLenKey, double maxCostKey) { StringBuilder typesetTextBuilder = new StringBuilder(); // Output start of file TypeSettingMark startFileMark = _context.getTypeSettingMark(MarkPosition.START_OF_FILE); typesetTextBuilder.append(startFileMark.getMarkText()); // Output any file heading text set using the PRINT COMMENT directive String headerText = _context.getTypeSettingFileHeaderText(); if (headerText != null) { typesetTextBuilder.append(_context.getTypeSettingFileHeaderText()); } // Output key parameters TypeSettingMark parametersMark = _context.getTypeSettingMark(MarkPosition.PARAMETERS); String parametersText = parametersMark.getMarkText(); typesetTextBuilder.append(parametersText); for (BracketedKeyNode node : bracketedKey) { typesetTextBuilder .append(generateTypesetTextForBracketedKeyNode(node, outputHtml, displayCharacterNumbers)); } TypeSettingMark endKeyMark = _context.getTypeSettingMark(MarkPosition.END_OF_KEY); typesetTextBuilder.append(endKeyMark.getMarkText()); TypeSettingMark endFileMark = _context.getTypeSettingMark(MarkPosition.END_OF_FILE); typesetTextBuilder.append(endFileMark.getMarkText()); String typesetText = typesetTextBuilder.toString(); typesetText = typesetText.replaceAll("@nchar", Integer.toString(_context.getDataSet().getNumberOfCharacters())); typesetText = typesetText.replaceAll("@ncincl", Integer.toString(includedCharacters.size())); typesetText = typesetText.replaceAll("@ncinkey", Integer.toString(numCharactersUsedInKey)); typesetText = typesetText.replaceAll("@ntaxa", Integer.toString(_context.getDataSet().getMaximumNumberOfItems())); typesetText = typesetText.replaceAll("@ntincl", Integer.toString(includedItems.size())); typesetText = typesetText.replaceAll("@ntinkey", Integer.toString(numTaxaUsedInKey)); typesetText = typesetText.replaceAll("@rbase", formatDouble(_context.getRBase())); typesetText = typesetText.replaceAll("@abase", formatDouble(_context.getABase())); typesetText = typesetText.replaceAll("@reuse", formatDouble(_context.getReuse())); typesetText = typesetText.replaceAll("@varywt", formatDouble(_context.getVaryWt())); typesetText = typesetText.replaceAll("@nconf", Integer.toString(_context.getNumberOfConfirmatoryCharacters())); typesetText = typesetText.replaceAll("@avglen", formatDouble(avgLenKey)); typesetText = typesetText.replaceAll("@avgcost", formatDouble(avgCostKey)); typesetText = typesetText.replaceAll("@maxlen", formatDouble(maxLenKey)); typesetText = typesetText.replaceAll("@maxcost", formatDouble(maxCostKey)); List<Integer> includedCharacterNumbers = new ArrayList<Integer>(); for (Character ch : includedCharacters) { includedCharacterNumbers.add(ch.getCharacterId()); } List<Integer> includedItemNumbers = new ArrayList<Integer>(); for (Item it : includedItems) { includedItemNumbers.add(it.getItemNumber()); } // any backslashes that may occur in rangeSymbol need to be escaped // otherwise they will be omitted when we do // a String.replaceAll String rangeSymbol = Matcher .quoteReplacement(_context.getTypeSettingMark(MarkPosition.RANGE_SYMBOL).getMarkText()); typesetText = typesetText.replaceAll("@cmask", Utils.formatIntegersAsListOfRanges(includedCharacterNumbers, rangeSymbol)); typesetText = typesetText.replaceAll("@rel", KeyUtils.formatCharacterReliabilities(_context, ",", rangeSymbol)); typesetText = typesetText.replaceAll("@tmask", Utils.formatIntegersAsListOfRanges(includedItemNumbers, rangeSymbol)); typesetText = typesetText.replaceAll("@tabund", KeyUtils.formatCharacterReliabilities(_context, ",", rangeSymbol)); typesetText = typesetText.replaceAll("@preset", KeyUtils.formatPresetCharacters(_context)); // @preset - Preset characters. typesetFile.outputLine(typesetText); } private String generateTypesetTextForBracketedKeyNode(BracketedKeyNode node, boolean outputHtml, boolean displayCharacterNumbers) { // @node - Node number. // @from - Previous node. // @to - Next node. // @state - feature/state text. // @nrow - number of "destinations" for the current node. CharacterFormatter typesetCharFormatter = new CharacterFormatter(false, CommentStrippingMode.STRIP_ALL, AngleBracketHandlingMode.REMOVE, false, true); typesetCharFormatter.setRtfToHtml(outputHtml); ItemFormatter typesetItemFormatter = new ItemFormatter(false, CommentStrippingMode.STRIP_ALL, AngleBracketHandlingMode.REMOVE, false, false, true); typesetItemFormatter.setRtfToHtml(outputHtml); // Need to count the number of destinations, as multiple taxa identified // by the one line are counted as distinct destinations. int numDestinations = 0; StringBuilder nodeTextBuilder = new StringBuilder(); TypeSettingMark firstLineMark; if (node.getNodeNumber() == 1) { firstLineMark = _context.getTypeSettingMark(MarkPosition.FIRST_LEAD_OF_FIRST_NODE); } else { firstLineMark = _context.getTypeSettingMark(MarkPosition.FIRST_LEAD_OF_NODE); } TypeSettingMark subsequentLineMark = _context.getTypeSettingMark(MarkPosition.SUBSEQUENT_LEAD_OF_NODE); TypeSettingMark firstTaxonDestinationMark = _context .getTypeSettingMark(MarkPosition.FIRST_DESTINATION_OF_LEAD); TypeSettingMark subsequentTaxonDestinationMark = _context .getTypeSettingMark(MarkPosition.SUBSEQUENT_DESTINATION_OF_LEAD); TypeSettingMark afterTaxonNamesMark = _context.getTypeSettingMark(MarkPosition.AFTER_TAXON_NAME); TypeSettingMark nodeNumberDestinationMark = _context .getTypeSettingMark(MarkPosition.DESTINATION_OF_LEAD_NODE); TypeSettingMark afterNodeMark = _context.getTypeSettingMark(MarkPosition.AFTER_NODE); for (int i = 0; i < node.getNumberOfLines(); i++) { StringBuilder lineTextBuilder = new StringBuilder(); List<MultiStateAttribute> attributesForLine = node.getAttributesForLine(i); Object destinationForLine = node.getDestinationForLine(i); StringBuilder attributesTextBuilder = new StringBuilder(); for (int j = 0; j < attributesForLine.size(); j++) { MultiStateAttribute attr = attributesForLine.get(j); if (displayCharacterNumbers) { attributesTextBuilder.append("("); attributesTextBuilder.append(attr.getCharacter().getCharacterId()); attributesTextBuilder.append(") "); } attributesTextBuilder.append(typesetCharFormatter.formatCharacterDescription(attr.getCharacter())); attributesTextBuilder.append(" "); attributesTextBuilder.append(typesetCharFormatter.formatState(attr.getCharacter(), attr.getPresentStates().iterator().next())); // Don't put a semicolon/space after the last attribute // description if (j < attributesForLine.size() - 1) { attributesTextBuilder.append("; "); } } if (i == 0) { lineTextBuilder.append(firstLineMark.getMarkText()); } else { lineTextBuilder.append(subsequentLineMark.getMarkText()); } if (destinationForLine instanceof Integer) { int intDestination = (Integer) destinationForLine; String destinationText = nodeNumberDestinationMark.getMarkText(); destinationText = destinationText.replaceAll("@to", Integer.toString(intDestination)); lineTextBuilder.append(destinationText); numDestinations++; } else { List<Item> destinationTaxa = (List<Item>) destinationForLine; for (int j = 0; j < destinationTaxa.size(); j++) { numDestinations++; Item taxon = destinationTaxa.get(j); String formattedTaxonDescription = typesetItemFormatter.formatItemDescription(taxon); // Any backslashes in taxon description (from RTF // formatting) need to be escaped, // otherwise they will be omitted when we do a // String.replaceAll formattedTaxonDescription = Matcher.quoteReplacement(formattedTaxonDescription); String destinationText; if (j == 0) { destinationText = firstTaxonDestinationMark.getMarkText(); } else { destinationText = subsequentTaxonDestinationMark.getMarkText(); } destinationText = destinationText.replaceAll("@to", formattedTaxonDescription); lineTextBuilder.append(destinationText); } lineTextBuilder.append(afterTaxonNamesMark.getMarkText()); } String lineText = lineTextBuilder.toString(); // Need to escape any backslashes in attributes text (from RTF // formatting) otherwise they will be omitted when we do a // String.replaceAll() String attributesText = attributesTextBuilder.toString(); attributesText = Matcher.quoteReplacement(attributesText); lineText = lineText.replaceAll("@state", attributesText); nodeTextBuilder.append(lineText); } nodeTextBuilder.append(afterNodeMark.getMarkText()); String nodeText = nodeTextBuilder.toString(); nodeText = nodeText.replaceAll("@node", Integer.toString(node.getNodeNumber())); nodeText = nodeText.replaceAll("@from", Integer.toString(node.getBackReference())); nodeText = nodeText.replaceAll("@nrow", Integer.toString(numDestinations)); return nodeText; } private String formatDouble(double d) { return String.format("%.2f", d); } @Override public void preProcess(AbstractDirective<? extends AbstractDeltaContext> directive, String data) throws DirectiveException { if (directive instanceof IncludeCharacters || directive instanceof ExcludeCharacters || directive instanceof IncludeItems || directive instanceof ExcludeItems) { readInputFiles(); } _nestedObserver.preProcess(directive, data); } @Override public void postProcess(AbstractDirective<? extends AbstractDeltaContext> directive) throws DirectiveException { _nestedObserver.postProcess(directive); } @Override public void finishedProcessing() { _nestedObserver.finishedProcessing(); } @Override public void handleDirectiveProcessingException(AbstractDeltaContext context, AbstractDirective<? extends AbstractDeltaContext> directive, Exception ex) throws DirectiveException { _nestedObserver.handleDirectiveProcessingException(context, directive, ex); } }