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 PrintWriter
s 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 Comparator
s 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 Mapnull
,
* 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