PageRenderTime 100ms CodeModel.GetById 18ms app.highlight 70ms RepoModel.GetById 1ms app.codeStats 1ms

/src/jd/captcha/JAntiCaptcha.java

https://bitbucket.org/jorgenio/jdownloader
Java | 2022 lines | 1479 code | 249 blank | 294 comment | 391 complexity | c7b1cfed3d209b39158660b898c3aa15 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1//    jDownloader - Downloadmanager
   2//    Copyright (C) 2008  JD-Team support@jdownloader.org
   3//
   4//    This program is free software: you can redistribute it and/or modify
   5//    it under the terms of the GNU General Public License as published by
   6//    the Free Software Foundation, either version 3 of the License, or
   7//    (at your option) any later version.
   8//
   9//    This program is distributed in the hope that it will be useful,
  10//    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12//    GNU General Public License for more details.
  13//
  14//    You should have received a copy of the GNU General Public License
  15//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  16
  17package jd.captcha;
  18
  19import java.awt.Color;
  20import java.awt.Graphics;
  21import java.awt.GridBagLayout;
  22import java.awt.GridLayout;
  23import java.awt.Image;
  24import java.awt.event.ActionEvent;
  25import java.awt.event.ActionListener;
  26import java.awt.image.BufferedImage;
  27import java.io.File;
  28import java.io.FileFilter;
  29import java.io.IOException;
  30import java.lang.reflect.Method;
  31import java.util.ArrayList;
  32import java.util.Collections;
  33import java.util.Comparator;
  34import java.util.ListIterator;
  35import java.util.Vector;
  36import java.util.logging.Logger;
  37
  38import javax.imageio.ImageIO;
  39import javax.swing.ImageIcon;
  40import javax.swing.JButton;
  41import javax.swing.JCheckBox;
  42import javax.swing.JDialog;
  43import javax.swing.JFrame;
  44import javax.swing.JLabel;
  45import javax.swing.JOptionPane;
  46import javax.swing.JPanel;
  47import javax.swing.JScrollPane;
  48import javax.swing.JTextArea;
  49
  50import jd.captcha.configuration.JACScript;
  51import jd.captcha.gui.BasicWindow;
  52import jd.captcha.gui.ImageComponent;
  53import jd.captcha.gui.ScrollPaneWindow;
  54import jd.captcha.pixelgrid.Captcha;
  55import jd.captcha.pixelgrid.Letter;
  56import jd.captcha.utils.Utilities;
  57import jd.controlling.JDLogger;
  58import jd.gui.userio.DummyFrame;
  59import jd.http.Browser;
  60import jd.nutils.Executer;
  61import jd.nutils.JDHash;
  62import jd.nutils.io.JDIO;
  63import jd.utils.JDUtilities;
  64
  65import org.appwork.utils.Regex;
  66import org.appwork.utils.os.CrossSystem;
  67import org.w3c.dom.Document;
  68import org.w3c.dom.Element;
  69import org.w3c.dom.NamedNodeMap;
  70import org.w3c.dom.Node;
  71import org.w3c.dom.NodeList;
  72
  73/**
  74 * Diese Klasse stellt alle public Methoden zur captcha Erkennung zur Verfügung.
  75 * Sie verküpft Letter und captcha Klassen. Gleichzeitig dient sie als
  76 * Parameter-Dump.
  77 * 
  78 * @author JD-Team
  79 */
  80public class JAntiCaptcha {
  81
  82    /**
  83     * Logger
  84     */
  85    private static Logger logger = Utilities.getLogger();
  86
  87    /**
  88     * Testet die Angegebene Methode. Dabei werden analysebilder erstellt.
  89     * 
  90     * @param file
  91     */
  92    public static void testMethod(File file) {
  93        int checkCaptchas = 20;
  94        String code;
  95        String inputCode;
  96        int totalLetters = 0;
  97        int correctLetters = 0;
  98        File captchaFile;
  99        Image img;
 100        String methodName = file.getName();
 101        File captchaDir = new File(file.getAbsolutePath() + "/captchas");
 102        if (Utilities.isLoggerActive()) {
 103            logger.info("Test Method: " + methodName);
 104        }
 105        new JAntiCaptcha(methodName);
 106        File[] entries = captchaDir.listFiles(new FileFilter() {
 107            public boolean accept(File pathname) {
 108                // if(Utilities.isLoggerActive())logger.info(pathname.getName(
 109                // ));
 110                if (pathname.getName().endsWith(".jpg") || pathname.getName().endsWith(".png") || pathname.getName().endsWith(".gif")) {
 111
 112                    return true;
 113                } else {
 114                    return false;
 115                }
 116            }
 117
 118        });
 119        ScrollPaneWindow w = new ScrollPaneWindow();
 120        w.setTitle(" Test Captchas: " + file.getAbsolutePath());
 121
 122        w.resizeWindow(100);
 123        if (Utilities.isLoggerActive()) {
 124            logger.info("Found Testcaptchas: " + entries.length);
 125        }
 126        int testNum = Math.min(checkCaptchas, entries.length);
 127        if (Utilities.isLoggerActive()) {
 128            logger.info("Test " + testNum + " Captchas");
 129        }
 130        int i = 0;
 131        for (i = 0; i < testNum; i++) {
 132            captchaFile = entries[(int) (Math.random() * entries.length)];
 133
 134            logger.info("JJJJJJJJ" + captchaFile);
 135            img = Utilities.loadImage(captchaFile);
 136            w.setText(0, i, captchaFile.getName());
 137            w.setImage(1, i, img);
 138
 139            w.repack();
 140
 141            JAntiCaptcha jac = new JAntiCaptcha(methodName);
 142            // BasicWindow.showImage(img);
 143            Captcha cap = jac.createCaptcha(img);
 144            if (cap == null) {
 145                if (Utilities.isLoggerActive()) {
 146                    logger.severe("Captcha Bild konnte nicht eingelesen werden");
 147                }
 148                continue;
 149            }
 150
 151            w.setImage(2, i, cap.getImage());
 152            // BasicWindow.showImage(cap.getImageWithGaps(2));
 153            code = jac.checkCaptcha(captchaFile, cap);
 154            w.setImage(3, i, cap.getImage());
 155
 156            w.setText(4, i, "JAC:" + code);
 157
 158            w.repack();
 159
 160            inputCode = JOptionPane.showInputDialog("Bitte Captcha Code eingeben", code);
 161
 162            w.setText(5, i, "User:" + inputCode);
 163            w.repack();
 164            if (code == null) {
 165                code = "";
 166            }
 167            if (inputCode == null) {
 168                inputCode = "";
 169            }
 170            code = code.toLowerCase();
 171            inputCode = inputCode.toLowerCase();
 172            for (int x = 0; x < inputCode.length(); x++) {
 173                totalLetters++;
 174
 175                if (inputCode.length() == code.length() && inputCode.charAt(x) == code.charAt(x)) {
 176                    correctLetters++;
 177                }
 178            }
 179            if (Utilities.isLoggerActive()) {
 180                logger.info("Erkennung: " + correctLetters + "/" + totalLetters + " = " + Utilities.getPercent(correctLetters, totalLetters) + "%");
 181            }
 182        }
 183        w.setText(0, i + 1, "Erkennung: " + Utilities.getPercent(correctLetters, totalLetters) + "%");
 184        w.setText(4, i + 1, "Richtig: " + correctLetters);
 185        w.setText(5, i + 1, "Falsch: " + (totalLetters - correctLetters));
 186        JOptionPane.showMessageDialog(new JFrame(), "Erkennung: " + correctLetters + "/" + totalLetters + " = " + Utilities.getPercent(correctLetters, totalLetters) + "%");
 187    }
 188
 189    /**
 190     * Führt einen Testlauf mit den übergebenen Methoden durch
 191     * 
 192     * @param methods
 193     */
 194    public static void testMethods(File[] methods) {
 195        for (File element : methods) {
 196            JAntiCaptcha.testMethod(element);
 197        }
 198
 199    }
 200
 201    /**
 202     * Fenster die eigentlich nur zur Entwicklung sind um Basic GUI Elemente zu
 203     * haben
 204     */
 205    private BasicWindow              bw2;
 206
 207    private BasicWindow              bw3;
 208
 209    private JDialog                  f;
 210
 211    /**
 212     * Bildtyp. Falls dieser von jpg unterschiedlich ist, muss zuerst
 213     * konvertiert werden.
 214     */
 215    private String                   imageType;
 216
 217    /**
 218     * jas Script Instanz. Sie verarbneitet das JACScript und speichert die
 219     * Parameter
 220     */
 221    public JACScript                 jas;
 222    /**
 223     * Vector mit den Buchstaben aus der MTHO File
 224     */
 225    public ArrayList<Letter>         letterDB;
 226
 227    private int[][]                  letterMap    = null;
 228
 229    /**
 230     * Anzahl der Buchstaben im Captcha. Wird aus der jacinfo.xml gelesen
 231     */
 232    private int                      letterNum;
 233
 234    /**
 235     * ordnername der methode
 236     */
 237    private String                   methodDirName;
 238
 239    private boolean                  showDebugGui = false;
 240
 241    private Vector<ScrollPaneWindow> spw          = new Vector<ScrollPaneWindow>();
 242
 243    private Captcha                  workingCaptcha;
 244
 245    private boolean                  extern;
 246
 247    public boolean isExtern() {
 248        return extern;
 249    }
 250
 251    private String command;
 252
 253    private String dstFile;
 254
 255    private String srcFile;
 256
 257    private Image  sourceImage;
 258
 259    public JAntiCaptcha(String methodName) {
 260        JACMethod method = JACMethod.forServiceName(methodName);
 261        if (method == null) {
 262            logger.severe("no such method found! " + methodName);
 263            return;
 264        }
 265        methodDirName = method.getFileName();
 266
 267        getJACInfo();
 268
 269        jas = new JACScript(this, methodDirName);
 270        long time = System.currentTimeMillis();
 271        loadMTHFile();
 272
 273        time = System.currentTimeMillis() - time;
 274        System.out.println(time);
 275        if (Utilities.isLoggerActive()) {
 276            logger.fine("letter DB loaded: Buchstaben: " + letterDB.size());
 277        }
 278    }
 279
 280    /**
 281     * prüft den übergebenen Captcha und gibt den Code als String zurück. Das
 282     * lettersarray des Catchas wird dabei bearbeitet. Es werden decoedvalue,
 283     * avlityvalue und parent gesetzt WICHTIG: Nach dem Decoden eines Captcha
 284     * herrscht Verwirrung. Es stehen unterschiedliche Methoden zur Verfügung um
 285     * an bestimmte Informationen zu kommen: captcha.getDecodedLetters() gibt
 286     * Die letter aus der datenbank zurück. Deren werte sind nicht fest. Auf den
 287     * Wert von getvalityvalue und getValityPercent kann man sich absolut nicht
 288     * verlassen. Einzig getDecodedValue() lässt sich zuverlässig auslesen
 289     * captcha.getLetters() gibt die Wirklichen Letter des captchas zurück. Hier
 290     * lassen sich alle wichtigen Infos abfragen. z.B. ValityValue,
 291     * ValityPercent, Decodedvalue, etc. Wer immer das hier liest sollte auf
 292     * keinen fall den fehler machen und sich auf Wert aus dem getdecodedLetters
 293     * array verlassen
 294     * 
 295     * @param captcha
 296     *            Captcha instanz
 297     * @return CaptchaCode
 298     */
 299    public String checkCaptcha(File file, Captcha captcha) {
 300        if (extern) return callExtern();
 301        workingCaptcha = captcha;
 302        // Führe prepare aus
 303        jas.executePrepareCommands(file, captcha);
 304        Letter[] letters = captcha.getLetters(getLetterNum());
 305        if (letters == null) {
 306            captcha.setValityPercent(100.0);
 307            if (Utilities.isLoggerActive()) {
 308                logger.severe("Captcha konnte nicht erkannt werden!");
 309            }
 310            return null;
 311        }
 312        String ret = "";
 313        double correct = 0;
 314        LetterComperator akt;
 315
 316        // Scannen
 317        Vector<LetterComperator> newLettersVector = new Vector<LetterComperator>();
 318        for (int i = 0; i < letters.length; i++) {
 319            letters[i].setId(i);
 320            if (letters[i].detected != null) {
 321                akt = letters[i].detected;
 322            } else {
 323                akt = getLetter(letters[i]);
 324            }
 325            akt.getA().setId(i);
 326
 327            newLettersVector.add(akt);
 328
 329        }
 330        if (letters.length > getLetterNum()) {
 331            // sortieren
 332            Collections.sort(newLettersVector, new Comparator<LetterComperator>() {
 333                public int compare(LetterComperator obj1, LetterComperator obj2) {
 334
 335                    if (obj1.getValityPercent() < obj2.getValityPercent()) { return -1; }
 336                    if (obj1.getValityPercent() > obj2.getValityPercent()) { return 1; }
 337                    return 0;
 338                }
 339            });
 340
 341            // schlechte entfernen
 342            if (Utilities.isLoggerActive()) {
 343                logger.info(getLetterNum() + "");
 344            }
 345
 346            if (!jas.getBoolean("autoLetterNum")) {
 347                for (int i = newLettersVector.size() - 1; i >= getLetterNum(); i--) {
 348                    newLettersVector.remove(i);
 349                }
 350            }
 351            // Wieder in die richtige reihenfolge sortieren
 352            Collections.sort(newLettersVector, new Comparator<LetterComperator>() {
 353                public int compare(LetterComperator obj1, LetterComperator obj2) {
 354
 355                    if (obj1.getA().getId() < obj2.getA().getId()) { return -1; }
 356                    if (obj1.getA().getId() > obj2.getA().getId()) { return 1; }
 357                    return 0;
 358                }
 359            });
 360        }
 361
 362        if (getJas().getString("useLettercomparatorFilter") != null && getJas().getString("useLettercomparatorFilter").length() > 0) {
 363            String[] ref = getJas().getString("useLettercomparatorFilter").split("\\.");
 364            if (ref.length != 2) {
 365                captcha.setValityPercent(100.0);
 366                if (Utilities.isLoggerActive()) {
 367                    logger.severe("useLettercomparatorFilter should have the format Class.Method");
 368                }
 369                return null;
 370            }
 371            String cl = ref[0];
 372            String methodname = ref[1];
 373
 374            Class<?> newClass;
 375            try {
 376                newClass = Class.forName("jd.captcha.specials." + cl);
 377
 378                Class<?>[] parameterTypes = new Class[] { newLettersVector.getClass(), this.getClass() };
 379                Method method = newClass.getMethod(methodname, parameterTypes);
 380                Object[] arguments = new Object[] { newLettersVector, this };
 381                Object instance = null;
 382                method.invoke(instance, arguments);
 383
 384            } catch (Exception e) {
 385                if (Utilities.isLoggerActive()) {
 386                    logger.severe("Fehler in useLettercomparatorFilter:" + e.getLocalizedMessage() + " / " + getJas().getString("useLettercomparatorFilter"));
 387                }
 388                JDLogger.exception(e);
 389            }
 390
 391        }
 392        for (int i = 0; i < newLettersVector.size(); i++) {
 393            akt = newLettersVector.get(i);
 394
 395            if (akt == null || akt.getValityPercent() >= 100.0) {
 396                ret += "-";
 397                correct += 100.0;
 398            } else {
 399                ret += akt.getDecodedValue();
 400
 401                akt.getA().setId(i);
 402                correct += akt.getValityPercent();
 403
 404            }
 405            // if(Utilities.isLoggerActive())logger.finer("Validty: " +
 406            // correct);
 407        }
 408        if (newLettersVector.size() == 0) {
 409            captcha.setValityPercent(100.0);
 410
 411            return null;
 412        }
 413        captcha.setLetterComperators(newLettersVector.toArray(new LetterComperator[] {}));
 414
 415        if (Utilities.isLoggerActive()) {
 416            logger.finer("Vality: " + (int) (correct / newLettersVector.size()));
 417        }
 418        captcha.setValityPercent(correct / newLettersVector.size());
 419        return ret;
 420    }
 421
 422    /**
 423     * Exportiert die aktelle Datenbank als PNG einzelbilder
 424     */
 425    public void exportDB() {
 426        File path = Utilities.directoryChooser();
 427
 428        File file;
 429        BufferedImage img;
 430        int i = 0;
 431        for (Letter letter : letterDB) {
 432
 433            img = letter.getFullImage();
 434            file = new File(path + "/letterDB/" + i++ + "_" + letter.getDecodedValue() + ".png");
 435            file.mkdirs();
 436
 437            try {
 438                logger.info("Write Db: " + file);
 439                ImageIO.write(img, "png", file);
 440            } catch (IOException e) {
 441                JDLogger.exception(e);
 442            }
 443        }
 444    }
 445
 446    private BufferedImage toBufferedImage(Image i) {
 447        if (i instanceof BufferedImage) { return (BufferedImage) i; }
 448        Image img;
 449        img = new ImageIcon(i).getImage();
 450        BufferedImage b;
 451        b = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB);
 452        Graphics g = b.createGraphics();
 453        g.drawImage(img, 0, 0, null);
 454        g.dispose();
 455        return b;
 456    }
 457
 458    private String callExtern() {
 459        try {
 460            File file = JDUtilities.getResourceFile(this.srcFile);
 461            file.getParentFile().mkdirs();
 462            String ext = CrossSystem.getFileExtension(file.getName());
 463            ImageIO.write(toBufferedImage(this.sourceImage), ext, file);
 464        } catch (Exception e) {
 465            JDLogger.exception(e);
 466            return null;
 467        }
 468        Executer exec = new Executer(JDUtilities.getResourceFile(this.command).getAbsolutePath());
 469        exec.setRunin(JDUtilities.getResourceFile(this.command).getParent());
 470        exec.setWaitTimeout(30);
 471        exec.start();
 472        exec.waitTimeout();
 473        // String ret = exec.getOutputStream() + " \r\n " +
 474        // exec.getErrorStream();
 475
 476        String res = JDIO.readFileToString(JDUtilities.getResourceFile(this.dstFile));
 477        if (res == null) return null;
 478        return res.trim();
 479
 480    }
 481
 482    /**
 483     * Gibt den erkannten CaptchaText zurück
 484     * 
 485     * @param captchafile
 486     *            Pfad zum Bild
 487     * @return CaptchaCode
 488     */
 489    public String checkCaptcha(File captchafile) {
 490        if (Utilities.isLoggerActive()) {
 491            logger.finer("check " + captchafile);
 492        }
 493        Image captchaImage = Utilities.loadImage(captchafile);
 494        Captcha captcha = createCaptcha(captchaImage);
 495        if (captcha != null) captcha.setCaptchaFile(captchafile);
 496        // captcha.printCaptcha();
 497        return checkCaptcha(captchafile, captcha);
 498    }
 499
 500    /**
 501     * Factory Methode zur Captcha erstellung
 502     * 
 503     * @param captchaImage
 504     *            Image instanz
 505     * @return captcha
 506     */
 507    public Captcha createCaptcha(Image captchaImage) {
 508        this.sourceImage = captchaImage;
 509        if (extern) return null;
 510        if (captchaImage.getWidth(null) <= 0 || captchaImage.getHeight(null) <= 0) {
 511            if (Utilities.isLoggerActive()) {
 512                logger.severe("Image Dimensionen zu klein. Image hat keinen Inahlt. Pfad/Url prüfen!");
 513            }
 514            return null;
 515        }
 516        Captcha ret = Captcha.getCaptcha(captchaImage, this);
 517        if (ret == null) { return null; }
 518        ret.setOwner(this);
 519        return ret;
 520    }
 521
 522    /**
 523     * Aus gründen der geschwindigkeit wird die MTH XMl in einen vector
 524     * umgewandelt
 525     */
 526    private void createLetterDBFormMTH(Document mth) {
 527        letterDB = new ArrayList<Letter>();
 528        long start1 = System.currentTimeMillis();
 529        try {
 530
 531            if (mth == null || mth.getFirstChild() == null) { return; }
 532            NodeList nl = mth.getFirstChild().getChildNodes();
 533            Letter tmp;
 534            for (int i = 0; i < nl.getLength(); i++) {
 535                // Get child node
 536                Node childNode = nl.item(i);
 537                if (childNode.getNodeName().equals("letter")) {
 538                    NamedNodeMap att = childNode.getAttributes();
 539
 540                    tmp = new Letter();
 541                    tmp.setOwner(this);
 542                    String id = JDUtilities.getAttribute(childNode, "id");
 543                    if (!tmp.setTextGrid(childNode.getTextContent())) {
 544
 545                        logger.severe("Error in Letters DB line: " + i + ":" + childNode.getTextContent() + " id:" + id);
 546                        continue;
 547                    }
 548
 549                    if (id != null) {
 550                        tmp.setId(Integer.parseInt(id));
 551                    }
 552                    tmp.setSourcehash(att.getNamedItem("captchaHash").getNodeValue());
 553                    tmp.setDecodedValue(att.getNamedItem("value").getNodeValue());
 554                    tmp.setBadDetections(Integer.parseInt(JDUtilities.getAttribute(childNode, "bad")));
 555                    tmp.setGoodDetections(Integer.parseInt(JDUtilities.getAttribute(childNode, "good")));
 556                    letterDB.add(tmp);
 557                } else if (childNode.getNodeName().equals("map")) {
 558                    if (Utilities.isLoggerActive()) {
 559                        logger.fine("Parse LetterMap");
 560                    }
 561                    long start2 = System.currentTimeMillis();
 562                    String[] map = childNode.getTextContent().split("\\|");
 563                    letterMap = new int[map.length][map.length];
 564                    for (int x = 0; x < map.length; x++) {
 565                        String[] row = map[x].split("\\,");
 566                        for (int y = 0; y < map.length; y++) {
 567                            letterMap[x][y] = Integer.parseInt(row[y]);
 568                        }
 569
 570                    }
 571                    if (Utilities.isLoggerActive()) {
 572                        logger.fine("LetterMap Parsing time: " + (System.currentTimeMillis() - start2));
 573                    }
 574                }
 575            }
 576        } catch (Exception e) {
 577            JDLogger.exception(e);
 578            if (Utilities.isLoggerActive()) {
 579                logger.severe("Fehler bein lesen der MTH Datei!!. Methode kann nicht funktionieren!");
 580            }
 581
 582        }
 583        if (Utilities.isLoggerActive()) {
 584            logger.fine("Mth Parsing time: " + (System.currentTimeMillis() - start1));
 585        }
 586    }
 587
 588    /**
 589     * Diese methode trainiert einen captcha
 590     * 
 591     * @param captchafile
 592     *            File zum Bild
 593     * @param letterNum
 594     *            Anzahl der Buchstaben im captcha
 595     * @return int -1: Übersprungen Sonst: anzahl der richtig erkanten Letter
 596     */
 597
 598    private Document createXMLFromLetterDB() {
 599        Document xml = JDUtilities.parseXmlString("<jDownloader></jDownloader>", false);
 600        if (letterMap != null) {
 601            Element element = xml.createElement("map");
 602            xml.getFirstChild().appendChild(element);
 603            element.appendChild(xml.createTextNode(getLetterMapString()));
 604        }
 605
 606        int i = 0;
 607        for (Letter letter : letterDB) {
 608            Element element = xml.createElement("letter");
 609            xml.getFirstChild().appendChild(element);
 610            element.appendChild(xml.createTextNode(letter.getPixelString()));
 611            element.setAttribute("id", i++ + "");
 612            element.setAttribute("value", letter.getDecodedValue());
 613            element.setAttribute("captchaHash", letter.getSourcehash());
 614            element.setAttribute("good", letter.getGoodDetections() + "");
 615            element.setAttribute("bad", letter.getBadDetections() + "");
 616
 617        }
 618        return xml;
 619
 620    }
 621
 622    private void destroyScrollPaneWindows() {
 623        while (spw.size() > 0) {
 624            spw.remove(0).destroy();
 625        }
 626    }
 627
 628    /**
 629     * Zeigt die Momentane Library an. Buchstaben können gelöscht werden
 630     */
 631    public void displayLibrary() {
 632        if (letterDB == null || letterDB.size() == 0) { return; }
 633        // final BasicWindow w = BasicWindow.getWindow("Library: " +
 634        // letterDB.size() + " Datensätze", 400, 300);
 635        final JFrame w = new JFrame();
 636        // w.setLayout(new GridBagLayout());
 637        sortLetterDB();
 638        JPanel p = new JPanel(new GridLayout(letterDB.size() + 1, 3));
 639        w.add(new JScrollPane(p));
 640
 641        final Letter[] list = new Letter[letterDB.size()];
 642
 643        int y = 0;
 644        int i = 0;
 645        ListIterator<Letter> iter = letterDB.listIterator(letterDB.size());
 646        final ArrayList<Integer> rem = new ArrayList<Integer>();
 647        while (iter.hasPrevious()) {
 648            final Letter tmp = iter.previous();
 649            list[i] = tmp;
 650
 651            JLabel lbl = null;
 652            if ((tmp.getGoodDetections() == 0 && tmp.getBadDetections() > 3) || ((double) tmp.getBadDetections() / (double) tmp.getGoodDetections() >= 3)) {
 653                lbl = new JLabel("<html><p><font color=\"#ff0000\" " + "size=\"3\">" + tmp.getId() + ": " + tmp.getDecodedValue() + "(" + tmp.getGoodDetections() + "/" + tmp.getBadDetections() + ") Size: " + tmp.toPixelObject(0.85).getSize() + "</font> </p>" + "</html>");
 654            } else {
 655                lbl = new JLabel(tmp.getId() + ": " + tmp.getDecodedValue() + "(" + tmp.getGoodDetections() + "/" + tmp.getBadDetections() + ") Size: " + tmp.toPixelObject(0.85).getSize());
 656            }
 657
 658            ImageComponent img = new ImageComponent(tmp.getImage());
 659
 660            final JCheckBox bt = new JCheckBox("DELETE");
 661            final int ii = i;
 662            bt.addActionListener(new ActionListener() {
 663                public Integer id = ii;
 664
 665                public void actionPerformed(ActionEvent arg) {
 666                    JCheckBox src = ((JCheckBox) arg.getSource());
 667                    if (src.getText().equals("DELETE")) {
 668                        rem.add(id);
 669                    } else {
 670                        rem.remove(id);
 671                    }
 672                }
 673
 674            });
 675            p.add(lbl);
 676            p.add(img);
 677            p.add(bt);
 678            i++;
 679            y++;
 680            // if (y > 20) {
 681            // y = 0;
 682            // x += 6;
 683            // }
 684        }
 685        JButton b = new JButton("Invoke");
 686        p.add(b);
 687        b.addActionListener(new ActionListener() {
 688
 689            public void actionPerformed(ActionEvent e) {
 690                // System.out.println(rem + "");
 691                ArrayList<Letter> list = new ArrayList<Letter>();
 692                int s = letterDB.size();
 693                for (Integer i : rem) {
 694                    try {
 695                        Letter let = letterDB.get(s - 1 - i);
 696                        list.add(let);
 697
 698                    } catch (Exception ew) {
 699                        JDLogger.exception(ew);
 700                    }
 701                }
 702                for (Letter letter : list) {
 703                    removeLetterFromLibrary(letter);
 704                }
 705                saveMTHFile();
 706                displayLibrary();
 707            }
 708        });
 709        w.pack();
 710        w.setVisible(true);
 711    }
 712
 713    public String getCodeFromFileName(String name) {
 714        return new Regex(name, "captcha_(.*?)_code(.*?)\\.(.*?)").getMatch(1);
 715    }
 716
 717    /**
 718     * Liest den captchaornder aus
 719     * 
 720     * @param path
 721     * @return File Array
 722     */
 723    public File[] getImages(String path) {
 724        File dir = new File(path);
 725
 726        if (dir == null || !dir.exists()) {
 727            if (Utilities.isLoggerActive()) {
 728                logger.severe("Image dir nicht gefunden " + path);
 729            }
 730
 731        }
 732        logger.info(dir + "");
 733        File[] entries = dir.listFiles(new FileFilter() {
 734            public boolean accept(File pathname) {
 735                if (Utilities.isLoggerActive()) {
 736                    logger.info(pathname.getName());
 737                }
 738                if (pathname.getName().endsWith(".bmp") || pathname.getName().endsWith(".jpg") || pathname.getName().endsWith(".png") || pathname.getName().endsWith(".gif")) {
 739
 740                    return true;
 741                } else {
 742                    return false;
 743                }
 744            }
 745
 746        });
 747        return entries;
 748
 749    }
 750
 751    /**
 752     * @return the imageType
 753     */
 754    public String getImageType() {
 755        return imageType;
 756    }
 757
 758    /**
 759     * Die Methode parsed die jacinfo.xml
 760     */
 761    private void getJACInfo() {
 762        File f = getResourceFile("jacinfo.xml");
 763        if (!f.exists()) {
 764            if (Utilities.isLoggerActive()) {
 765                logger.severe("jacinfo.xml is missing2");
 766            }
 767            return;
 768        }
 769        Document doc = JDUtilities.parseXmlString(JDIO.readFileToString(f), false);
 770        if (doc == null) {
 771            if (Utilities.isLoggerActive()) {
 772                logger.severe("jacinfo.xml is missing2");
 773            }
 774            return;
 775        }
 776
 777        NodeList nl = doc.getFirstChild().getChildNodes();
 778        for (int i = 0; i < nl.getLength(); i++) {
 779            // Get child node
 780            Node childNode = nl.item(i);
 781
 782            if (childNode.getNodeName().equals("method")) {
 783                try {
 784                    this.extern = JDUtilities.getAttribute(childNode, "type").equalsIgnoreCase("extern");
 785                } catch (Exception e) {
 786                }
 787            } else if (childNode.getNodeName().equals("command")) {
 788
 789                this.srcFile = JDUtilities.getAttribute(childNode, "src");
 790                this.dstFile = JDUtilities.getAttribute(childNode, "dst");
 791                this.command = JDUtilities.getAttribute(childNode, "cmd");
 792
 793            } else if (childNode.getNodeName().equals("format")) {
 794                try {
 795                    setLetterNum(Integer.parseInt(JDUtilities.getAttribute(childNode, "letterNum")));
 796                } catch (Exception e) {
 797                }
 798
 799                setImageType(JDUtilities.getAttribute(childNode, "type"));
 800            }
 801        }
 802    }
 803
 804    /**
 805     * @return JACscript Instanz
 806     */
 807    public JACScript getJas() {
 808        return jas;
 809    }
 810
 811    /**
 812     * Vergleicht a und b und gibt eine Vergleichszahl zurück. a und b werden
 813     * gegeneinander verschoben und b wird über die Parameter gedreht. Praktisch
 814     * heißt das, dass derjenige Treffer als gut eingestuft wird, bei dem der
 815     * Datenbank Datensatz möglichst optimal überdeckt wird.
 816     * 
 817     * @param a
 818     *            Original Letter
 819     * @param B
 820     *            Vergleichsletter
 821     * @return int 0(super)-0xffffff (ganz übel)
 822     */
 823    public LetterComperator getLetter(Letter letter) {
 824        if (jas.getDouble("quickScanValityLimit") <= 0) {
 825            logger.info("quickscan disabled");
 826            return getLetterExtended(letter);
 827
 828        }
 829
 830        logger.info("Work on Letter:" + letter);
 831        // long startTime = Utilities.getTimer();
 832        LetterComperator res = null;
 833        double lastPercent = 100.0;
 834        int bvX, bvY;
 835        try {
 836
 837            if (letterDB == null) {
 838                if (Utilities.isLoggerActive()) {
 839                    logger.severe("letterDB nicht vorhanden");
 840                }
 841                return null;
 842            }
 843
 844            LetterComperator lc;
 845            ScrollPaneWindow w = null;
 846            if (isShowDebugGui()) {
 847                w = new ScrollPaneWindow();
 848
 849                w.setTitle(" Letter " + letter.getId());
 850            }
 851            bvX = jas.getInteger("borderVarianceX");
 852            bvY = jas.getInteger("borderVarianceY");
 853            int line = 0;
 854            lc = new LetterComperator(letter, null);
 855            lc.setScanVariance(0, 0);
 856            lc.setOwner(this);
 857            res = lc;
 858            int tt = 0;
 859            logger.info("Do quickscan");
 860            Method preValueFilterMethod = null;
 861            Class<?>[] preValueFilterParameterTypes = null;
 862            Object[] preValueFilterArguments = new Object[] { null, this };
 863            if (jas.getString("preValueFilter").length() > 0) {
 864                String[] ref = jas.getString("preValueFilter").split("\\.");
 865                if (ref.length != 2) {
 866                    if (Utilities.isLoggerActive()) {
 867                        logger.severe("preValueFilter should have the format Class.Method");
 868                    }
 869                    return null;
 870                }
 871                String cl = ref[0];
 872                String methodname = ref[1];
 873                Class<?> newClass;
 874                try {
 875                    newClass = Class.forName("jd.captcha.specials." + cl);
 876                    preValueFilterParameterTypes = new Class[] { LetterComperator.class, this.getClass() };
 877                    preValueFilterMethod = newClass.getMethod(methodname, preValueFilterParameterTypes);
 878
 879                } catch (Exception e) {
 880                    JDLogger.exception(e);
 881                }
 882            }
 883            Method postValueFilterMethod = null;
 884            Class<?>[] postValueFilterParameterTypes = null;
 885            Object[] postValueFilterArguments = new Object[] { null, this };
 886            if (jas.getString("postValueFilter").length() > 0) {
 887                String[] ref = jas.getString("postValueFilter").split("\\.");
 888                if (ref.length != 2) {
 889                    if (Utilities.isLoggerActive()) {
 890                        logger.severe("postValueFilter should have the format Class.Method");
 891                    }
 892                    return null;
 893                }
 894                String cl = ref[0];
 895                String methodname = ref[1];
 896                Class<?> newClass;
 897                try {
 898                    newClass = Class.forName("jd.captcha.specials." + cl);
 899                    postValueFilterParameterTypes = new Class[] { LetterComperator.class, this.getClass() };
 900                    postValueFilterMethod = newClass.getMethod(methodname, postValueFilterParameterTypes);
 901
 902                } catch (Exception e) {
 903                    JDLogger.exception(e);
 904                }
 905            }
 906            for (Letter tmp : letterDB) {
 907                if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
 908
 909                if (Math.abs(tmp.getHeight() - letter.getHeight()) > bvY || Math.abs(tmp.getWidth() - letter.getWidth()) > bvX) {
 910                    continue;
 911                }
 912
 913                lc = new LetterComperator(letter, tmp);
 914                // commented out only experimental
 915                // lc.setScanVariance(0, 0);
 916                lc.setOwner(this);
 917
 918                if (preValueFilterMethod != null) {
 919                    preValueFilterArguments[0] = tmp;
 920                    preValueFilterArguments[1] = lc;
 921                    if (!((Boolean) preValueFilterMethod.invoke(null, preValueFilterArguments))) {
 922                        continue;
 923                    }
 924
 925                }
 926                lc.run();
 927                tt++;
 928                if (isShowDebugGui()) {
 929                    w.setText(0, line, "0° Quick " + tt);
 930                    w.setImage(1, line, lc.getA().getImage(2));
 931                    w.setText(2, line, lc.getA().getDim());
 932                    w.setImage(3, line, lc.getB().getImage(2));
 933                    w.setText(4, line, lc.getB().getDim());
 934                    w.setImage(5, line, lc.getIntersectionLetter().getImage(2));
 935                    w.setText(6, line, lc.getIntersectionLetter().getDim());
 936                    w.setText(7, line, lc);
 937                    line++;
 938                }
 939                postValueFilterArguments[0] = lc;
 940                if (postValueFilterMethod == null || (Boolean) postValueFilterMethod.invoke(null, postValueFilterArguments)) {
 941                    if (res == null || lc.getValityPercent() < res.getValityPercent()) {
 942                        if (res != null && res.getValityPercent() < lastPercent) {
 943                            lastPercent = res.getValityPercent();
 944                        }
 945                        res = lc;
 946                        if (jas.getDouble("LetterSearchLimitPerfectPercent") >= lc.getValityPercent()) {
 947                            if (Utilities.isLoggerActive()) {
 948                                logger.finer(" Perfect Match: " + res.getB().getDecodedValue() + res.getValityPercent() + " good:" + tmp.getGoodDetections() + " bad: " + tmp.getBadDetections() + " - " + res);
 949                            }
 950                            res.setDetectionType(LetterComperator.QUICKSCANPERFECTMATCH);
 951                            res.setReliability(lastPercent - res.getValityPercent());
 952                            return res;
 953                        }
 954                        // if(Utilities.isLoggerActive())logger.finer("dim "
 955                        // +
 956                        // lc.getA().getDim() + "|" + lc.getB().getDim() + " New
 957                        // Best value: " + lc.getDecodedValue() + " "
 958                        // +lc.getValityPercent() + " good:" +
 959                        // tmp.getGoodDetections() + " bad: " +
 960                        // tmp.getBadDetections() + " - " + lc);
 961                    } else if (res != null) {
 962                        if (lc.getValityPercent() < lastPercent) {
 963                            lastPercent = lc.getValityPercent();
 964                        }
 965                    }
 966                }
 967            }
 968
 969        } catch (Exception e) {
 970
 971            JDLogger.exception(e);
 972        }
 973        if (res != null && res.getB() != null) {
 974            if (Utilities.isLoggerActive()) {
 975                logger.finer(" Normal Match: " + res.getB().getDecodedValue() + " " + res.getValityPercent() + " good:" + res.getB().getGoodDetections() + " bad: " + res.getB().getBadDetections());
 976            }
 977            // if (Utilities.isLoggerActive()) logger.fine("Letter erkannt
 978            // in: " + (Utilities.getTimer() - startTime) + " ms");
 979            res.setReliability(lastPercent - res.getValityPercent());
 980            if (res.getReliability() >= jas.getDouble("quickScanReliabilityLimit") && res.getValityPercent() < jas.getDouble("quickScanValityLimit")) {
 981                res.setDetectionType(LetterComperator.QUICKSCANMATCH);
 982                logger.info("Qickscan found " + res.getValityPercent() + "<" + jas.getDouble("quickScanValityLimit"));
 983                return res;
 984            } else {
 985                if (Utilities.isLoggerActive()) {
 986                    logger.warning("Letter nicht ausreichend erkannt. Try Extended " + res.getReliability() + " - " + jas.getDouble("quickScanReliabilityLimit") + " /" + res.getValityPercent() + "-" + jas.getDouble("quickScanValityLimit"));
 987                }
 988                return getLetterExtended(letter);
 989            }
 990        } else {
 991            if (Utilities.isLoggerActive()) {
 992                logger.warning("Letter nicht erkannt. Try Extended");
 993            }
 994            return getLetterExtended(letter);
 995        }
 996
 997    }
 998
 999    /**
1000     * Sucht in der MTH ANch dem besten übereinstimmenem letter
1001     * 
1002     * @param letter
1003     *            (refferenz)
1004     * @return Letter. Beste Übereinstimmung
1005     */
1006    private LetterComperator getLetterExtended(Letter letter) {
1007        // long startTime = Utilities.getTimer();
1008        LetterComperator res = null;
1009        logger.info("Extended SCAN");
1010        double lastPercent = 100.0;
1011        JTextArea tf = null;
1012        try {
1013
1014            if (letterDB == null) {
1015                if (Utilities.isLoggerActive()) {
1016                    logger.severe("letterDB nicht vorhanden");
1017                }
1018                return null;
1019            }
1020
1021            Letter tmp;
1022            int leftAngle = jas.getInteger("scanAngleLeft");
1023            int rightAngle = jas.getInteger("scanAngleRight");
1024            if (leftAngle > rightAngle) {
1025                int temp = leftAngle;
1026                leftAngle = rightAngle;
1027                rightAngle = temp;
1028                if (Utilities.isLoggerActive()) {
1029                    logger.warning("param.scanAngleLeft>paramscanAngleRight");
1030                }
1031            }
1032            int steps = Math.max(1, jas.getInteger("scanAngleSteps"));
1033            boolean turnDB = jas.getBoolean("turnDB");
1034            int angle;
1035            Letter orgLetter = letter;
1036            LetterComperator lc;
1037
1038            ScrollPaneWindow w = null;
1039            if (isShowDebugGui()) {
1040                w = new ScrollPaneWindow();
1041
1042                w.setTitle(" Letter " + letter.getId());
1043            }
1044            int line = 0;
1045            lc = new LetterComperator(letter, null);
1046            lc.setOwner(this);
1047            res = lc;
1048
1049            Method preValueFilterMethod = null;
1050            Class<?>[] preValueFilterParameterTypes = null;
1051            Object[] preValueFilterArguments = new Object[] { null, this };
1052            if (jas.getString("preValueFilter").length() > 0) {
1053                String[] ref = jas.getString("preValueFilter").split("\\.");
1054                if (ref.length != 2) {
1055                    if (Utilities.isLoggerActive()) {
1056                        logger.severe("preValueFilter should have the format Class.Method");
1057                    }
1058                    return null;
1059                }
1060                String cl = ref[0];
1061                String methodname = ref[1];
1062                Class<?> newClass;
1063                try {
1064                    newClass = Class.forName("jd.captcha.specials." + cl);
1065                    preValueFilterParameterTypes = new Class[] { LetterComperator.class, this.getClass() };
1066                    preValueFilterMethod = newClass.getMethod(methodname, preValueFilterParameterTypes);
1067
1068                } catch (Exception e) {
1069                    JDLogger.exception(e);
1070                }
1071            }
1072            Method postValueFilterMethod = null;
1073            Class<?>[] postValueFilterParameterTypes = null;
1074            Object[] postValueFilterArguments = new Object[] { null, this };
1075            if (jas.getString("postValueFilter").length() > 0) {
1076                String[] ref = jas.getString("postValueFilter").split("\\.");
1077                if (ref.length != 2) {
1078                    if (Utilities.isLoggerActive()) {
1079                        logger.severe("postValueFilter should have the format Class.Method");
1080                    }
1081                    return null;
1082                }
1083                String cl = ref[0];
1084                String methodname = ref[1];
1085                Class<?> newClass;
1086                try {
1087                    newClass = Class.forName("jd.captcha.specials." + cl);
1088                    postValueFilterParameterTypes = new Class[] { LetterComperator.class, this.getClass() };
1089                    postValueFilterMethod = newClass.getMethod(methodname, postValueFilterParameterTypes);
1090
1091                } catch (Exception e) {
1092                    JDLogger.exception(e);
1093                }
1094            }
1095            for (angle = Utilities.getJumperStart(leftAngle, rightAngle); Utilities.checkJumper(angle, leftAngle, rightAngle); angle = Utilities.nextJump(angle, leftAngle, rightAngle, steps)) {
1096
1097                if (turnDB) {
1098                    letter = orgLetter;
1099                } else {
1100                    letter = orgLetter.turn(angle);
1101                }
1102                // if(Utilities.isLoggerActive())logger.finer(" Angle " +
1103                // angle + " : " + letter.getDim());
1104
1105                int tt = 0;
1106                for (Letter ltr : letterDB) {
1107                    if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
1108
1109                    if (turnDB) {
1110                        tmp = ltr.turn(angle);
1111
1112                    } else {
1113                        tmp = ltr;
1114                    }
1115
1116                    if (Math.abs(tmp.getHeight() - letter.getHeight()) > jas.getInteger("borderVarianceY") || Math.abs(tmp.getWidth() - letter.getWidth()) > jas.getInteger("borderVarianceX")) {
1117                        continue;
1118                    }
1119
1120                    lc = new LetterComperator(letter, tmp);
1121                    lc.setOwner(this);
1122
1123                    if (preValueFilterMethod != null) {
1124                        preValueFilterArguments[0] = lc;
1125                        preValueFilterArguments[1] = this;
1126                        if (!((Boolean) preValueFilterMethod.invoke(null, preValueFilterArguments))) {
1127                            continue;
1128                        }
1129
1130                    }
1131                    lc.run();
1132                    // if(Utilities.isLoggerActive())logger.info("Duration:
1133                    // "+(Utilities.getTimer()-timer) +"
1134                    // Loops: "+lc.loopCounter);
1135                    tt++;
1136
1137                    if (isShowDebugGui()) {
1138                        w.setText(0, line, angle + "° " + tt);
1139                        w.setImage(1, line, lc.getA().getImage(2));
1140                        w.setText(2, line, lc.getA().getDim());
1141                        w.setImage(3, line, lc.getB().getImage(2));
1142                        w.setText(4, line, lc.getB().getDim());
1143                        w.setImage(5, line, lc.getIntersectionLetter().getImage(2));
1144                        w.setText(6, line, lc.getIntersectionLetter().getDim());
1145
1146                        w.setComponent(7, line, tf = new JTextArea());
1147                        tf.setText(lc.toString());
1148                        if (lc.getPreValityPercent() > jas.getInteger("preScanFilter") && jas.getInteger("preScanFilter") > 0) {
1149                            tf.setBackground(Color.LIGHT_GRAY);
1150                        }
1151                        line++;
1152                    }
1153                    postValueFilterArguments[0] = lc;
1154                    if (postValueFilterMethod == null || (Boolean) postValueFilterMethod.invoke(null, postValueFilterArguments)) {
1155
1156                        if (res == null || lc.getValityPercent() < res.getValityPercent()) {
1157                            if (res != null && res.getValityPercent() < lastPercent) {
1158                                lastPercent = res.getValityPercent();
1159                            }
1160                            res = lc;
1161
1162                            if (jas.getDouble("LetterSearchLimitPerfectPercent") >= lc.getValityPercent()) {
1163                                res.setDetectionType(LetterComperator.PERFECTMATCH);
1164                                res.setReliability(lastPercent - res.getValityPercent());
1165                                if (Utilities.isLoggerActive()) {
1166                                    logger.finer(" Perfect Match: " + res.getB().getDecodedValue() + " " + res.getValityPercent() + " good:" + tmp.getGoodDetections() + " bad: " + tmp.getBadDetections() + " - " + res);
1167                                }
1168                                if (isShowDebugGui()) {
1169                                    tf.setBackground(Color.GREEN);
1170                                }
1171                                return res;
1172                            }
1173                            if (isShowDebugGui()) {
1174                                tf.setBackground(Color.BLUE);
1175                            }
1176                            if (Utilities.isLoggerActive()) {
1177                                logger.finer("Angle " + angle + "dim " + lc.getA().getDim() + "|" + lc.getB().getDim() + " New Best value: " + lc.getDecodedValue() + " " + lc.getValityPercent() + " good:" + tmp.getGoodDetections() + " bad: " + tmp.getBadDetections() + " - " + lc);
1178                            }
1179
1180                        } else if (res != null) {
1181                            // if (Utilities.isLoggerActive()&&
1182                            // lc.getDecodedValue().equalsIgnoreCase("G"))
1183                            // logger.finer("Angle " + angle + "dim " +
1184                            // lc.getA().getDim() + "|" + lc.getB().getDim() + "
1185                            // value: " + lc.getDecodedValue() + " " +
1186                            // lc.getValityPercent() + " good:" +
1187                            // tmp.getGoodDetections() + " bad: " +
1188                            // tmp.getBadDetections() + " - " + lc);
1189
1190                            if (lc.getValityPercent() < lastPercent) {
1191                                lastPercent = lc.getValityPercent();
1192                            }
1193                        }
1194                    }
1195
1196                }
1197                // if(Utilities.isLoggerActive())logger.info("Full Angle scan
1198                // in
1199                // "+(Utilities.getTimer()-startTime2));
1200            }
1201            // w.refreshUI();
1202        } catch (Exception e) {
1203            JDLogger.exception(e);
1204        }
1205
1206        if (res != null && res.getB() != null) {
1207            if (Utilities.isLoggerActive()) {
1208                logger.finer(" Normal Match: " + res.getB().getDecodedValue() + " " + res.getValityPercent() + " good:" + res.getB().getGoodDetections() + " bad: " + res.getB().getBadDetections());
1209            }
1210
1211            res.setReliability(lastPercent - res.getValityPercent());
1212        } else {
1213            if (getJas().getInteger("preScanEmergencyFilter") > getJas().getInteger("preScanFilter")) {
1214                logger.warning("nicht erkannt. Verwende erweiterte Emergencydatenbank");
1215                int psf = getJas().getInteger("preScanFilter");
1216                getJas().set("preScanFilter", getJas().getInteger("preScanEmergencyFilter"));
1217                LetterComperator ret = getLetterExtended(letter);
1218                getJas().set("preScanFilter", psf);
1219                return ret;
1220
1221            }
1222            if (Utilities.isLoggerActive()) {
1223                logger.severe("Letter entgültig nicht erkannt");
1224            }
1225            if (isShowDebugGui() && tf != null) {
1226                tf.setBackground(Color.RED);
1227            }
1228
1229        }
1230
1231        return res;
1232
1233    }
1234
1235    /**
1236     * @return gibt die Lettermap als String zurück
1237     */
1238    private String getLetterMapString() {
1239        StringBuilder ret = new StringBuilder();
1240        int i = 0;
1241        for (int x = 0; x < letterMap.length; x++) {
1242            ret.append("|");
1243            i++;
1244            for (int y = 0; y < letterMap[0].length; y++) {
1245
1246                ret.append(letterMap[x][y]);
1247                i++;
1248                ret.append(",");
1249                i++;
1250            }
1251            ret.deleteCharAt(ret.length() - 1);
1252            if (Utilities.isLoggerActive()) {
1253     

Large files files are truncated, but you can click here to view the full file