import java.io.*; import java.util.*; /** * Fußnoten-Sortierprogramm mit Ausrichtung auf eine möglichst hohe Effizienz * und Skalierbarkeit sowie einem geringen Speicherverbrauch. *

* Zugunsten der Verarbeitungsgeschwindigkeit wurde auf die Nutzung von * regulären Ausdrücken (Pattern/Matcher), * sowie des PrintWriters verzichtet.
* Zugunsten des Speicherverbrauchs arbeitet das Programm Stream-basiert, * was jedoch die re-nummerierung der im Text befindlichen Verweise in * der Reihenfolge der Verweisziele aus dem Fußnotenabschnitt ohne * zweiten Durchlauf unmöglich macht. Des Weiteren wurde auf die Verwendung * eines Comparators verzichtet, der die Vorhaltung aller * Verweisziele im Speicher bedingt hätte um die Sortierung durchführen zu * können.
* Nachteil der Optimierungen ist ein deutlich längerer Code - die Klassen * FootnoteFinder und Footnotes, sowie die vielen * writer.write() würde es ohne diese Optimierungen nicht geben. *

* Die Formatierung des Codes, die Stern-Imports, das fehlende * Exception-Handling, sowie die Verwendung innerer Klassen dienen * ausschließlich der möglichst kompakten Umsetzung der gestellten Aufgabe * und kann nicht als Beispiel guten Codes dienen. *

* Dieser Code wurde mit Eclipse 3.4 / Java 6 unter Kubuntu 8.04 erstellt. :-) * * @author Oliver Siegmar */ public class FootnoteProcessor { /** Fußnotenmarkierung **/ private static final String FOOT_MARKER = "@footnote:"; /** Zeichensatz der Quelldatei **/ private static final String CHARSET = "ISO-8859-15"; /** Betriebssystemspezifisches Zeilenende **/ private static final String NEWLINE = System.getProperty("line.separator"); /** Zeichen, mit dem die Fußnoten begonnen werden **/ private static final char FOOTNOTE_OPEN = '['; /** Zeichen, mit dem die Fußnoten geschlossen werden **/ private static final char FOOTNOTE_CLOSE = ']'; /** * Speicher für die neu nummerierten Fußnoten * (Key = alte Nummer, Value = neue Nummer) **/ private final Map noteMap = new HashMap(); /** Writer für die Ausgabe des re-nummerierten Textes **/ private final Writer writer; public static void main(final String[] args) throws IOException { if (args.length == 0) { System.err.println("No file specified. " + "Usage: FootnoteProcessor input.txt > output.txt"); System.exit(1); } new FootnoteProcessor().processFile(new File(args[0])); } private FootnoteProcessor() throws IOException { writer = new BufferedWriter(new OutputStreamWriter(System.out)); } /** * Einstiegsmethode zur Verarbeitung der Daten. * * @param file die zu verarbeitende Quelldatei. * @throws IOException tritt bei Lese- oder Schreibfehlern auf. */ private void processFile(final File file) throws IOException { final BufferedReader br = new BufferedReader(new InputStreamReader( new FileInputStream(file), CHARSET)); Scanner scanner = new BodyScanner(); for (String line; (line = br.readLine()) != null;) { if (FOOT_MARKER.equals(line)) { scanner = new FootScanner(); writer.write(FOOT_MARKER); writer.write(NEWLINE); } else { scanner.process(line); } } br.close(); writer.close(); } /** * Klasse zur effizienten Ermittlung der Position von Fußnoten */ private class FootnoteFinder { /** Zu untersuchende Zeile **/ private final char[] ca; /** Startposition des Fußnotenmarkers ([) **/ private int start; /** Endposition des Fußnotenmarkers (]) **/ private int end; /** * Initialisierung. * * @param line die zu untersuchende Zeile. */ public FootnoteFinder(final String line) { ca = line.toCharArray(); } /** * Sucht nach der nächsten Fußnote * * @return Start- und Endposition der Fußnote, oder null, * wenn keine (weitere) Fußnote gefunden wurde. */ private int[] find() { boolean open = false; int[] ret = null; for (int i = end; i < ca.length; i++) { if (!open) { if (ca[i] == FOOTNOTE_OPEN) { start = i + 1; open = true; } } else { if (ca[i] == FOOTNOTE_CLOSE) { if (i > start) { ret = new int[] { start, i }; } end = i + 1; break; } else if (ca[i] < '0' || ca[i] > '9') { break; } } } return ret; } } /** Interface für den zeilenbasierten Fußnotenscanner **/ private interface Scanner { /** * Verarbeitet eine Zeile der Quelldatei. * @param line eingelesene Zeile der Quelldatei. * @throws IOException tritt bei einem Schreibfehler auf. */ void process(String line) throws IOException; } /** Von den Scannerimplementierungen gemeinsam genutzte Funktionalität **/ private abstract class AbstractScanner implements Scanner { public void process(final String line) throws IOException { final FootnoteFinder matcher = new FootnoteFinder(line); for (int[] matches; (matches = matcher.find()) != null;) { process(line, matches[0], matches[1]); } postProcess(line); } /** * Diese Methode wird für jeden Treffer einer Fußnote aufgerufen. * * @param line die zu verarbeitende Zeile. * @param start Startposition der Fußnote. * @param end Endposition der Fußnote. * @throws IOException tritt bei einem Schreibfehler auf. */ abstract void process(String line, int start, int end) throws IOException; /** * Diese Methode wird aufgerufen, sobald alle Treffer für eine * Zeile abgearbeitet sind. * * @param line die zu verarbeitende Zeile. * @throws IOException tritt bei einem Schreibfehler auf. */ void postProcess(final String line) throws IOException {} } /** Scannerimplementierung, welche die Verweise im Text untersucht **/ private class BodyScanner extends AbstractScanner { private int lastNumber = 0; private int lastEnd = 0; @Override public void process(final String line, final int begin, final int end) throws IOException { final Integer number = new Integer(line.substring(begin, end)); Integer remap = noteMap.get(number); if (remap == null) { remap = ++lastNumber; noteMap.put(number, remap); } writer.write(line, lastEnd, (begin - lastEnd)); writer.write(Integer.toString(remap)); lastEnd = end; } @Override public void postProcess(final String line) throws IOException { writer.write(line, lastEnd, (line.length() - lastEnd)); writer.write(NEWLINE); lastEnd = 0; } } /** Scannerimplementierung, welche die Verweisziele untersucht **/ private class FootScanner extends AbstractScanner { private final Footnotes footnotes = new Footnotes(); @Override public void process(final String line, final int begin, final int end) throws IOException { final Integer number = new Integer(line.substring(begin, end)); final String text = line.substring(end + 2); final Integer remap = noteMap.get(number); footnotes.add(remap, text); } } /** Speicher-, Sortier- und Ausgabelogik für Verweisziele **/ private class Footnotes { /** Zwischenspeicher für Fußnoten, die noch nicht ausgegeben wurden **/ private final Map footnotes = new HashMap(); /** Ziffer der Fußnote, die als nächstes ausgegeben werden muss **/ private Integer nextNumber = 1; /** * Fügt dem Zwischenspeicher eine Fußnote hinzu. Prüft anschließend, * ob die hinzugefügte Fußnote (sowie evtl. bereits gespeicherte) * Fußnoten ausgegeben werden können. * * @param number Nummer der hinzuzufügenden Fußnote. * @param footnote Text der hinzuzufügenden Fußnote. * @throws IOException tritt bei einem Schreibfehler auf. */ public void add(final Integer number, final String footnote) throws IOException { footnotes.put(number, footnote); if (number.equals(nextNumber)) { printFootnotes(); } } /** * Gibt alle im Zwischenspeicher befindlichen Fußnoten in * sortierter Reihenfolge aus. Bricht ab, wenn der Zwischenspeicher * noch keine Fußnote der nächsten Nummer findet. * * @throws IOException tritt bei einem Schreibfehler auf. */ private void printFootnotes() throws IOException { String nextFootnote; while ((nextFootnote = footnotes.get(nextNumber)) != null) { writer.write(FOOTNOTE_OPEN); writer.write(nextNumber.toString()); writer.write(FOOTNOTE_CLOSE); writer.write(" "); writer.write(nextFootnote); writer.write(NEWLINE); nextNumber++; footnotes.remove(nextFootnote); } } } }