/*
 * $Id: Analyser.java,v 1.28 2006/08/31 19:59:11 marbobo Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: Analyser.java,v $
 * Revision 1.28  2006/08/31 19:59:11  marbobo
 * + Refactoring
 *
 * Revision 1.27  2006/07/18 11:42:11  lawern
 * Stoppwörter aus den Testvektoren für die Optimierung rausgeschmissen.
 *
 * Revision 1.26  2006/07/17 14:16:08  lawern
 * Umstellung der Schlagwort- und Summary-Extraktion auf TFIDF-Gewichtung.
 *
 * Revision 1.25  2006/07/17 13:04:01  lawern
 * Neuer typografiegewichteter Index und k-NN-Klassifikation aus meiner Diss.
 *
 * Revision 1.24  2006/03/23 11:37:13  marbobo
 * + Mögliche NPE abgefangen
 *
 * Revision 1.23  2005/06/23 01:01:42  aram
 * + Anpassungen bzgl. Cluster-Moduls
 *
 * Revision 1.22  2005/04/14 09:36:36  lawern
 * Fehler in Init-Reihenfolge korrigiert.
 *
 * Revision 1.21  2005/04/14 09:02:55  lawern
 * Stemming überarbeitet und erstmal ausgeschaltet.
 *
 * Revision 1.20  2005/04/13 06:37:26  lawern
 * Analyser mit Typografiegewichtung
 *
 * Revision 1.19  2004/11/10 15:02:53  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.18  2004/06/27 17:08:23  marbobo
 * + Quellcode maintenance
 *
 * Revision 1.17  2004/03/08 18:23:53  bigmac
 * Anpassungen an VKCLanguageSerializer und VKCLanguagePackSerializer
 *
 * Revision 1.16  2003/12/24 10:12:05  bigmac
 * Projektweite Import-Optimierung
 *
 * Revision 1.15  2003/11/07 11:59:54  lawern
 * Analyse wird jetzt abgebrochen, wenn weniger als 10MB Speicher frei sind.
 *
 * Revision 1.14  2003/09/09 08:40:43  lawern
 * Zusätzliche Abfrage für Retriever eingebaut.
 *
 * Revision 1.13  2003/08/27 09:44:15  lawern
 * LangPackId hinzugefügt
 *
 * Revision 1.12  2003/07/31 13:59:31  bigmac
 * Compilerwarnungen beseitigt
 *
 * Revision 1.11  2003/06/27 12:20:55  lawern
 * Ins Summary kommen jetzt nur noch Sätze mit '.' oder '!' am Ende.
 *
 * Revision 1.10  2003/05/26 11:04:15  lawern
 * Bugfix
 *
 * Revision 1.9  2003/04/28 14:15:01  lawern
 * Bugfix
 *
 * Revision 1.8  2003/04/28 07:50:58  lawern
 * Überarbeitung von rebuildSentences
 *
 * Revision 1.7  2003/04/25 17:36:28  lawern
 * Überarbeitung der Satzendeerkennung
 *
 * Revision 1.6  2003/04/25 15:43:09  lawern
 * letzte Änderung rückgängig gemacht
 *
 * Revision 1.5  2003/04/25 13:58:03  lawern
 * Bugfix
 *
 * Revision 1.4  2003/04/25 13:45:21  lawern
 * Anpassung an Dynamische Language-Module
 *
 * Revision 1.3  2003/04/23 10:21:58  lawern
 * Liefert jetzt auch die Tokens für Lucene
 *
 * Revision 1.2  2003/03/20 09:11:35  lawern
 * Konverter und Analyse überarbeitet
 *
 * Revision 1.1  2003/03/07 11:56:56  lawern
 * initiale Version
 *
 */
package vkc.analyse;

import cern.colt.list.DoubleArrayList;

import vkc.converter.HtmlExtractor;
import vkc.converter.TdfElement;

import vkc.db.entities.VKCLanguagePack;

import vkc.db.serializer.VKCLanguagePackSerializer;

import vkc.misc.VKCLogManager;
import vkc.search.Search;

import java.io.File;

import java.lang.reflect.Method;

import java.text.BreakIterator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;


/**
 * Klasse zum Analysieren von Texten im VKC-TextStyle-Format.
 */
public abstract class Analyser {
    public static final boolean ONLY_KEYWORDS_DE = false; // nimm nur Substantive aus deutschen Texten
    public static final boolean debug = false;
    public static boolean typographyWeighting = true;
    public static double powWeightingFactor = 1.5; //wird nur bei weighting = POW_WEIGHTING berücksichtigt
    public static boolean globalStemming = false;
    public static boolean stopwords = true;
    public static final boolean normalize = true; //Normalisierung auf die Anzahl der Wörter
    static boolean initialized = false;
    static Locale currentLocale = Locale.ENGLISH;
    private static int numberOfFactories;
    private static int[] counter;
    private static HashSet[] stopLists;
    private static Class[] analyser;
    protected HashMap words;
    protected ArrayList sentences;
    protected ArrayList tokens;
    protected double documentWeight = 0;
    public boolean differentWeights = false;
    boolean stemming;

    /**
     * Initialisiert die Analyser-Komponente beim ersten Aufruf.
     */
    public static void init(String languageDir) {
        loadLanguageFactories(languageDir);
        initialized = true;
    }

    /**
     * Liefert <code>true</code> zurück, wenn der Speicher voll ist.
     */
    private static final boolean memoryFull(Runtime rTime) {
        long freeMemory = rTime.freeMemory();

        if (freeMemory < 10000000) {
            //Gibts evtl. noch nicht allokierten Speicher?
            freeMemory = rTime.maxMemory() - rTime.totalMemory() + freeMemory;

            if (freeMemory < 10000000) {
                //versuchen ob der GarbageCollector noch etwas erreichen kann.
                rTime.gc();
                freeMemory = rTime.maxMemory() - rTime.totalMemory() +
                    rTime.freeMemory();

                if (freeMemory < 10000000) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Diese Methode erzeugt auf Basis der übergebenen TdfElemente einen
     * geeigneten Analyser.
     *
     * @param elements Collection mit den Basiselementen in Form von
     *        TdfElement-Objekten
     *
     * @return Analyser oder <code>null</code>,falls keiner erzeugt werden
     *         konnte
     */
    public static synchronized Analyser createAnalyser(Collection elements) {
        return createAnalyser(elements, -1, 1, 1, globalStemming);
    }

    /**
     * Diese Methode erzeugt auf Basis der übergebenen TdfElemente einen
     * geeigneten Analyser.
     *
     * @param elements Collection mit den Basiselementen in Form von
     *        TdfElement-Objekten
     * @param stemming Gibt an, ob ein sprachabhängiges Stemming durchgeführt werden soll oder nicht.
     *
     * @return Analyser oder <code>null</code>,falls keiner erzeugt werden
     *         konnte
     */
    public static synchronized Analyser createAnalyser(Collection elements,
        boolean stemming) {
        return createAnalyser(elements, -1, 1, 1, stemming);
    }

    /**
     * Diese Methode erzeugt auf Basis der übergebenen TdfElemente einen
     * geeigneten Analyser.
     *
     * @param elements Collection mit den Basiselementen in Form von
     *        TdfElement-Objekten
     * @param language Erzeugt einen Analyser in der angegebenen Sprache
     *
     * @return Analyser oder <code>null</code>,falls keiner erzeugt werden
     *         konnte
     */
    public static synchronized Analyser createAnalyser(Collection elements,
        int language, int mostUsedWeight, int minWeight, boolean stemming) {
        boolean diffWeights = false;

        if (elements == null) {
            return null;
        }

        //Counter auf 0 setzen
        for (int i = numberOfFactories - 1; i >= 0; i--) {
            counter[i] = 0;
        }

        double weight = 1;

        boolean languageDetected;
        Class winnerAnalyser = null;

        if (language != -1) {
            languageDetected = true;
            winnerAnalyser = analyser[language];
        } else {
            languageDetected = false;
        }

        ArrayList words = new ArrayList();

        //Zeilenende: -1
        //Absatzende: -2
        DoubleArrayList weights = new DoubleArrayList();

        BreakIterator wb = BreakIterator.getWordInstance(currentLocale);
        String line;
        String currentWord;
        TdfElement element;
        int winnerCounter = 0;
        int secondCounter = 0;
        int countValue;

        Iterator it = elements.iterator();

        Runtime rTime = Runtime.getRuntime();

        while (it.hasNext()) {
            //prüfen ob noch genug Speicher vorhanden ist
            if (memoryFull(rTime)) {
                VKCLogManager.errorLog("vkc.analyse.Analyser", "5",
                    "Too low memory for full analyse!");

                break;
            }

            element = (TdfElement) it.next();

            if (element.weight != 1.0) {
                diffWeights = true;
            }

            if (typographyWeighting) {
                weight = element.weight * element.weight;
            }

            line = element.text;
            wb.setText(line);

            if (element.separated) {
                //Absatzende einfuegen
                words.add(null);
                weights.add(-2.0);
            } else {
                if (element.newLine) {
                    //Zeilenende einfuegen
                    words.add(null);
                    weights.add(-1.0);
                }
            }

            int start = wb.first();

            for (int end = wb.next(); end != BreakIterator.DONE;
                    start = end, end = wb.next()) {
                currentWord = line.substring(start, end).trim();

                if (currentWord.length() > 0) {
                    words.add(currentWord);
                    weights.add(weight);
                }

                if (!languageDetected) {
                    //Wort in Stoppwortlisten der verfuegbaren Sprachen suchen und fuer
                    //jeweilige Sprache werten, bis die Sprache signifikant erkannt ist.
                    //D.h. es müssen für die Siegersprache mindestens 100 Stoppwörter gefunden 
                    //sein und der zweitplatzierte darf maximal halbsoviel Stoppwörter zählen. 
                    currentWord = currentWord.toLowerCase().trim();

                    if (currentWord.length() > 0) {
                        for (int i = numberOfFactories - 1; i >= 0; i--) {
                            if (stopLists[i] != null) {
                                if (stopLists[i].contains(currentWord)) {
                                    counter[i]++;
                                }
                            }
                        }
                    }
                }
            }

            if (!languageDetected) {
                //die Counter nur einmal pro Element prüfen
                for (int i = numberOfFactories - 1; i >= 0; i--) {
                    countValue = counter[i];

                    if (countValue >= winnerCounter) {
                        winnerCounter = countValue;
                        winnerAnalyser = analyser[i];
                    } else {
                        if (countValue > secondCounter) {
                            secondCounter = countValue;
                        }
                    }
                }

                //prüfen ob Sprache signifikant erkannt ist
                if ((winnerCounter >= 100) &&
                        (((winnerCounter + 2) / (secondCounter + 1)) >= 2)) {
                    languageDetected = true;
                }
            }
        }

        //Html-Dokument enthält keinen Text!
        if (weights.size() == 0) {
            return null;
        }

        //Textende ist auch das Absatzende!
        if (weights.getQuick(weights.size() - 1) != -2.0) {
            words.add(null);
            weights.add(-2.0);
        }

        //Analyser auf Basis der Sprachanalyse auswaehlen.
        Analyser createdAnalyser;

        if (debug) {
            for (int i = 0; i < numberOfFactories; i++) {
                System.out.println(analyser[i].getName() + ": " + counter[i]);
            }

            System.out.println("winnerCounter: " + winnerCounter);
            System.out.println("secondCounter: " + secondCounter);
        }

        if (languageDetected) {
            //Sprache sicher erkannt
            try {
                if (winnerAnalyser != null) {
                    createdAnalyser = (Analyser) winnerAnalyser.newInstance();
                    VKCLogManager.debug(Analyser.class, "Language detected!");
                } else {
                    throw new Exception();
                }
            } catch (Exception e) {
                VKCLogManager.errorLog(Analyser.class, "1",
                    "Language not detected!", e);
                createdAnalyser = new DefaultAnalyser();
            }
        } else {
            if (winnerCounter >= 100) {
                //Sprache konnte nicht sicher erkannt werden
                createdAnalyser = new DefaultAnalyser();
                VKCLogManager.infoLog("vkc.analyse.Analyser",
                    "Language not significant detected!");
            } else {
                if (winnerCounter < 2) {
                    //Sprache gar nicht erkannt
                    createdAnalyser = new DefaultAnalyser();
                    VKCLogManager.infoLog("vkc.analyse.Analyser",
                        "Language not detected! Too few stop words.");
                } else {
                    if (((winnerCounter + 2) / (secondCounter + 1)) >= 2) {
                        //Dokument enthält sehr wenig Stoppwörter
                        try {
                            if (winnerAnalyser != null) {
                                createdAnalyser = (Analyser) winnerAnalyser.newInstance();

                                if (debug) {
                                    VKCLogManager.infoLog("vkc.analyse.Analyser",
                                        "Language detected!");
                                }
                            } else {
                                throw new Exception();
                            }
                        } catch (Exception e) {
                            VKCLogManager.errorLog("vkc.analyse.Analyser", "2",
                                "Language not detected!", e);
                            createdAnalyser = new DefaultAnalyser();
                        }
                    } else {
                        //Sprache gar nicht erkannt
                        createdAnalyser = new DefaultAnalyser();
                        VKCLogManager.infoLog("vkc.analyse.Analyser",
                            "Language not detected!");
                    }
                }
            }
        }

        createdAnalyser.stemming = stemming;
        createdAnalyser.init_Analyser(words, weights);
        createdAnalyser.differentWeights = diffWeights;

        return createdAnalyser;
    }

    /**
     * Initialisiert den jeweiligen Analyser.
     */
    abstract void init_Analyser(ArrayList words, DoubleArrayList weights);

    /**
     * Fügt den übergebenen Satz mit seiner Schlagwortliste dem Ergebnis hinzu.
     *
     * @param sentence Satztext
     * @param keyWords Liste der im Satz vorkommenden Schlagwörter in Form von
     *        SummaryEntries
     *
     * @return Verweis auf den Ergebniseintrag, anderenfalls <code>null</code>
     */
    protected abstract SummaryEntry setSentence(String sentence,
        ArrayList keyWords);

    /**
     * Fügt das übergebene Wort mit seinem Gewicht in die Ergebnisliste ein,
     * falls es sich um ein Schlagwort handelt und liefert in diesem Fall den
     * Verweis auf den Eintrag zurück, anderenfalls wird null zurückgeliefert.
     *
     * @param word Wort welches hinzugefügt werden soll
     * @param weigt Gewicht des hinzuzufügenden Wortes
     *
     * @return Verweis auf den Ergebniseintrag, anderenfalls <code>null</code>
     */
    protected abstract SummaryEntry setWord(String word, double weight);

    /**
     * Baut aus der übergebenen Wörterliste wieder Sätze mit Gewichten auf.
     *
     * @param words Wörterliste
     * @param weights Liste mit Gewichten der Wörter der Wörterliste
     */
    protected void rebuildSentences(ArrayList words, DoubleArrayList weights) {
        double currentWeight;
        int charValue;
        int position = 0;
        String numValue = "1234567890";

        String lastWord = " ";
        String currentWord;
        String nextWord = " ";
        SummaryEntry currentEntry;

        StringBuffer currentSentence = new StringBuffer(1000);
        ArrayList currentSentenceEntries = new ArrayList();

        boolean spaceBefore = false;
        boolean spaceAfter = false;
        boolean lastSpace = false;
        boolean firstQuotation = true;
        boolean firstApostrophe = true;
        boolean finisher = false;

        int wordLength = 0;
        int wordsSize = words.size();

        Runtime rTime = Runtime.getRuntime();

        for (int i = 0; i < wordsSize; i++) {
            if (memoryFull(rTime)) {
                VKCLogManager.errorLog("vkc.analyse.Analyser", "6",
                    "Too low memory for full analyse!");

                break;
            }

            lastSpace = spaceAfter;
            currentWord = (String) words.get(i);

            if ((i + 1) < wordsSize) {
                nextWord = (String) words.get(i + 1);

                if (nextWord == null) {
                    nextWord = " ";
                }
            } else {
                nextWord = " ";
            }

            currentWeight = weights.getQuick(i);

            if (debug) {
                System.out.println(currentWord + ":" + currentWeight);
            }

            //System.out.print(":"+currentWord+":");
            if (currentWord != null) {
                wordLength = currentWord.length();

                if (wordLength <= 1) {
                    //currentWord ist Einzelzeichen
                    currentEntry = null;

                    charValue = "\"'-([¿¡)];°,.:!?".indexOf(currentWord);

                    switch (charValue) {
                    case 0: //Anführungszeichen

                        if (firstQuotation) {
                            firstQuotation = false;
                            spaceBefore = true;
                            spaceAfter = false;
                        } else {
                            firstQuotation = true;
                            spaceBefore = false;
                            spaceAfter = true;
                        }

                        finisher = false;

                        break;

                    case 1: //Apostroph

                        if (firstApostrophe) {
                            firstApostrophe = false;
                            spaceBefore = true;
                            spaceAfter = false;
                        } else {
                            firstApostrophe = true;
                            spaceBefore = false;
                            spaceAfter = true;
                        }

                        finisher = false;

                        //"Lars's 'Test'" wird richtig erfasst, da "Lars's" ein Wort ist.
                        break;

                    case 2: //Bindestrich
                        spaceBefore = false;
                        spaceAfter = false;
                        finisher = false;

                        break;

                    case 3: //Zeichen nach denen kein Space kommt
                    case 4:
                    case 5:
                    case 6:
                        spaceBefore = true;
                        spaceAfter = false;
                        finisher = false;

                        break;

                    case 7: //Zeichen vor denen kein Space kommt
                    case 8:
                    case 9:
                    case 10:
                        spaceBefore = false;
                        spaceAfter = true;
                        finisher = false;

                        break;

                    case 11: //Komma
                        spaceBefore = false;
                        finisher = false;

                        if (numValue.indexOf(nextWord.charAt(0)) > -1) {
                            //Dezimalkomma
                            spaceAfter = false;
                        } else {
                            spaceAfter = true;
                        }

                        break;

                    case 12: //Punkt
                        spaceBefore = false;

                        //testen ob letztes Wort eine Abkürzung ist
                        if (isAbbreviation(lastWord + ".".toLowerCase())) {
                            //Wort ist Abkürzung => kein Satzende
                            finisher = false;
                            spaceAfter = true;
                        } else {
                            if (numValue.indexOf(lastWord.charAt(lastWord.length() -
                                            1)) > -1) {
                                //vor dem Punkt steht eine Ziffer
                                if (numValue.indexOf(nextWord.charAt(0)) > -1) {
                                    //Dezimalpunkt
                                    spaceAfter = false;
                                } else {
                                    spaceAfter = true;
                                }

                                finisher = false;
                            } else {
                                //wahrscheinlich Satzende, da vorher keine Ziffer
                                finisher = true;
                                spaceAfter = true;
                            }
                        }

                        break;

                    case 13: //Zeichen die auf ein Satzende hinweisen
                    case 14:
                    case 15:
                        spaceBefore = false;
                        spaceAfter = true;
                        finisher = true;

                        break;

                    default: //unbekanntes Satzzeichen oder Einzelbuchstabe z.B. "I"
                        spaceBefore = true;
                        spaceAfter = true;
                        finisher = false;
                    }
                } else {
                    //currentWord ist Wort
                    spaceBefore = true;
                    spaceAfter = true;
                    finisher = false;

                    //Wort in Statistik schreiben
                    currentEntry = setWord(currentWord, currentWeight);

                    if (currentEntry != null) {
                        //wenn das Wort ein Schlagwort ist muss es in die Token-Liste
                        //für Lucene
                        tokens.add(new TdfToken(currentEntry.getText(),
                                position, currentWeight));
                    }
                }

                //Satz erweitern
                if (lastSpace && spaceBefore) {
                    currentSentence.append(" ");
                }

                currentSentence.append(currentWord);

                //Keyword für Satz eintragen, wenn setWord es akzeptiert hat
                if (currentEntry != null) {
                    currentSentenceEntries.add(currentEntry);
                }
            } else {
                //currentWord ist Zeilen- oder Absatzwechsel
                if (currentWeight == -2.0) {
                    //Absatzwechsel
                    //Anführungszeichen und Apostrophe gelten nicht über Absätze
                    firstQuotation = true;
                    firstApostrophe = true;
                    finisher = true;
                } else {
                    finisher = false;
                }
            }

            //Wort-Position im Text erhöhen
            position += wordLength;

            if (finisher) {
                //Satz abspeichern, da vollständig
                if (currentSentence.length() > 0) {
                    //Satz nur hinzufügen, wenn am Ende ein '.' oder '!' steht.
                    char lastChar = currentSentence.charAt(currentSentence.length() -
                            1);

                    if ((lastChar == '.') || (lastChar == '!')) {
                        setSentence(currentSentence.toString(),
                            currentSentenceEntries);
                    }
                }

                currentSentence = new StringBuffer(1000);
                currentSentenceEntries = new ArrayList();
                spaceAfter = false;

                //System.out.println();
            }

            if (currentWord != null) {
                lastWord = currentWord;
            } else {
                currentWord = " ";
            }
        }
    }

    abstract boolean isAbbreviation(String word);

    /**
     * Berechnung von Schluesselwörtern
     *
     * @param keys Anzahl der zu berechnenden Schluesselwörter. Falls words==-1
     *        ist, werden alle Keywords als Ergebnis geliefert.
     *
     * @return Array mit Objekten vom Typ SummaryEntry. null, falls keine
     *         Keywords gefunden wurden.
     */
    public SummaryEntry[] getKeyWords(int words) {
        return getKeyWords(words, false);
    }

    /**
     * Berechnung von Schluesselwörtern
     *
     * @param keys Anzahl der zu berechnenden Schluesselwörter. Falls words==-1
     *        ist, werden alle Keywords als Ergebnis geliefert.
     *
     * @return Array mit Objekten vom Typ SummaryEntry. null, falls keine
     *         Keywords gefunden wurden.
     */
    public SummaryEntry[] getKeyWords(int words, boolean idfWeighting) {
        int size = this.words.size();
        SummaryEntry[] keys = new SummaryEntry[size];
        keys = (SummaryEntry[]) this.words.values().toArray(keys);

        if (normalize) {
            Arrays.sort(keys,
                new KeyComparator(documentWeight, idfWeighting,
                    getLangPackId().intValue()));
        } else {
            Arrays.sort(keys,
                new KeyComparator(idfWeighting, getLangPackId().intValue()));
        }

        //Hier stellt sich die Frage: Muss man immer das gesamte Array sortieren?
        //Eigenlich nicht, da oft nur die 5 größten Einträge gebraucht werden. Das
        //lohnt aber kaum, da bei es ab einer Array-Größe von 1000 Einträgen und 10
        //geforderten Schlagwörtern so schneller ist.
        if (words > keys.length) {
            words = keys.length;
        }

        if (words >= 0) {
            SummaryEntry[] resultKeys = new SummaryEntry[words];
            System.arraycopy(keys, 0, resultKeys, 0, words);

            return resultKeys;
        }

        return keys;
    }

    /**
     * Zusammenfassung berechnen
     *
     * @param sentences - Anzahl der Sätze die geliefert werden sollen
     *
     * @return Liste vom Typ SummaryEntry
     */
    public SummaryEntry[] getSummary(int sentences) {
        int size = this.sentences.size();
        SummaryEntry[] summary = new SummaryEntry[size];
        summary = (SummaryEntry[]) this.sentences.toArray(summary);

        Arrays.sort(summary, new SentenceComparator(documentWeight, getLangPackId().intValue()));

        //Hier stellt sich die Frage: Muss man immer das gesamte Array sortieren?
        //Eigenlich nicht, da oft nur die 5 größten Einträge gebraucht werden. Das
        //lohnt aber kaum, da bei es ab einer Array-Größe von 1000 Einträgen und 10
        //geforderten Schlagwörtern so schneller ist.
        if (sentences > summary.length) {
            sentences = summary.length;
        }

        if (sentences >= 0) {
            SummaryEntry[] resultSentences = new SummaryEntry[sentences];
            System.arraycopy(summary, 0, resultSentences, 0, sentences);

            return resultSentences;
        }

        return summary;
    }

    /**
     * Liefert für die Indizierung in Lucene alle Tokens mit TDF-Gewicht
     * zurück.
     *
     * @return Liste mit Objekten vom Typ TdfToken
     */
    public List getTokens() {
        return tokens;
    }

    /**
     * Läd alle verfügbaren Language-Factories.
     */
    private static void loadLanguageFactories(String languageDir) {
        File file = new File(languageDir);
        File[] files = file.listFiles();
        String fileName;
        String extension;
        String[] param = { languageDir };
        Integer[] param2 = new Integer[1];
        Class[] paramTypes = new Class[1];
        Class[] paramTypes2 = new Class[1];

        try {
            paramTypes[0] = Class.forName("java.lang.String");
            paramTypes2[0] = Class.forName("java.lang.Integer");
        } catch (ClassNotFoundException e1) {
        }

        ArrayList extensions = new ArrayList();

        //Liste der Analyser-Extensions erstellen 
        for (int i = files.length - 1; i >= 0; i--) {
            fileName = files[i].getName();

            if (fileName.indexOf("stopwords_") == 0) {
                extension = fileName.substring(10, 12);
                extensions.add(extension);
            }
        }

        //Arrays einrichten
        numberOfFactories = extensions.size();
        counter = new int[numberOfFactories];
        stopLists = new HashSet[numberOfFactories];
        analyser = new Class[numberOfFactories];

        //Arrays initialisieren;
        Class tmpClass;
        Method tmpMethod;
        VKCLanguagePack languagePack;

        try {
            for (int i = numberOfFactories - 1; i >= 0; i--) {
                extension = (String) extensions.get(i);

                //zugehöriges VKCLanguagePack von der Datenbank holen
                languagePack = VKCLanguagePackSerializer.getLanguagePackByCountryCode(extension);

                if (((languagePack != null) && (languagePack.isEnabled()))) {
                    //Stoppwortliste holen
                    tmpClass = Class.forName("vkc.analyse.Analyser_" +
                            extension);
                    tmpMethod = tmpClass.getMethod("getStopWords", paramTypes);
                    analyser[i] = tmpClass;
                    stopLists[i] = (HashSet) tmpMethod.invoke(null, (Object[])param);

                    //LangPackId aus der Datenbank setzen
                    param2[0] = languagePack.getLangPackId();
                    tmpMethod = tmpClass.getMethod("setLangPackId", paramTypes2);
                    tmpMethod.invoke(null, (Object[])param2);

                    VKCLogManager.infoLog("vkc.analyse.Analyser",
                        "Language pack found: " + extension + " ID:" +
                        languagePack.getLangPackId());
                } else {
                    stopLists[i] = null;
                }
            }
        } catch (Exception e) {
            VKCLogManager.errorLog("vkc.analyse.Analyser", "4",
                "Error while retrieve LanguagePacks!", e);
        }
    }

    /**
     * Liest die ID des Language Pack.
     */
    public abstract Integer getLangPackId();

    /**
     * Main-Methode zum Testen der Funktion
     */
    public static void main(String[] args) {
        VKCLogManager.init("c:/temp/test_", ".log");
        VKCLogManager.setEnableLogging(true);
        VKCLogManager.setLogToConsole(true);

        HtmlExtractor extractor = new HtmlExtractor();
        extractor.render(args[0]);
        extractor.analyse();

        TextStyles elements = extractor.getHtmlElements();

        Analyser analyser = Analyser.createAnalyser(elements, false);

        SummaryEntry[] keys = analyser.getKeyWords(10);

        for (int i = 0; i < 10; i++)
            System.out.println(keys[i].getText() + ":" + keys[i].getWeight());

        SummaryEntry[] summary = analyser.getSummary(5);

        for (int i = 0; i < 5; i++)
            System.out.println(summary[i].getText() + ":" +
                summary[i].getWeight());

        System.exit(0);
    }
}


final class KeyComparator implements Comparator {
    double words;
    private boolean normalize = false;
    private boolean idfWeighting = false;
    private int langPackId = -1;
    private int docSize;

    public KeyComparator(boolean idfWeighting, int langPackId) {
        this.idfWeighting = idfWeighting;
        this.langPackId = langPackId;
        if (idfWeighting)
        docSize = Search.getNumDocs();
    }

    public KeyComparator(double words, boolean idfWeighting, int langPackId) {
        this.words = words;
        this.idfWeighting = idfWeighting;
        this.langPackId = langPackId;
        if (idfWeighting)
            docSize = Search.getNumDocs();
        normalize = true;
    }

    public int compare(Object o1, Object o2) {
        if (idfWeighting) {
            if (normalize) {
                double o1Val = ((SummaryEntry) o1).getIDFWeight(words,
                        langPackId, docSize);
                double o2Val = ((SummaryEntry) o2).getIDFWeight(words,
                        langPackId, docSize);

                return ((o2Val < o1Val) ? (-1) : ((o2Val == o1Val) ? 0 : 1));
            }

            double o1Val = ((SummaryEntry) o1).getIDFWeight(langPackId, docSize);
            double o2Val = ((SummaryEntry) o2).getIDFWeight(langPackId, docSize);

            return ((o2Val < o1Val) ? (-1) : ((o2Val == o1Val) ? 0 : 1));
        }
        
        if (normalize) {
            double o1Val = ((SummaryEntry) o1).getWeight(words);
            double o2Val = ((SummaryEntry) o2).getWeight(words);

            return ((o2Val < o1Val) ? (-1) : ((o2Val == o1Val) ? 0 : 1));
        }

        double o1Val = ((SummaryEntry) o1).getWeight();
        double o2Val = ((SummaryEntry) o2).getWeight();

        return ((o2Val < o1Val) ? (-1) : ((o2Val == o1Val) ? 0 : 1));
    }

    public boolean equals(Object obj) {
        System.err.println("Diese Methode sollte niemals aufgerufen werden!!!");

        return false;
    }
}


final class SentenceComparator implements Comparator {
    double sentences;
    int langPackId = -1;
    int probSize;

    public SentenceComparator(double sentences, int langPackId) {
        this.sentences = sentences;
        this.langPackId = langPackId;
        probSize = Search.getNumDocs();
    }

    public int compare(Object o1, Object o2) {
        double o1Val = ((SummaryEntry) o1).getSentenceWeight(sentences, langPackId, probSize);
        double o2Val = ((SummaryEntry) o2).getSentenceWeight(sentences, langPackId, probSize);

        return ((o2Val < o1Val) ? (-1) : ((o2Val == o1Val) ? 0 : 1));
    }

    public boolean equals(Object obj) {
        System.err.println("Diese Methode sollte niemals aufgerufen werden!!!");

        return false;
    }
}


final class BufferedString {
    private String text;
    private String lowerText;
    private int hashCode;

    public BufferedString(String text) {
        this.text = text;
        this.lowerText = text.toLowerCase();
        hashCode = lowerText.hashCode();
    }

    public final int hashCode() {
        return hashCode;
    }

    public final boolean equals(Object test) {
        return lowerText.equals(((BufferedString) test).getLowerText());
    }

    public final void set(String text) {
        this.text = text;
        this.lowerText = text.toLowerCase();
        hashCode = lowerText.hashCode();
    }

    public final String get() {
        return text;
    }
    
    public final String getLowerText() {
        return lowerText;
    }
}
