Package org.freeplane.features.format

Source Code of org.freeplane.features.format.ScannerController

/*
*  Freeplane - mind map editor
*  Copyright (C) 2011 Volker Boerchers
*
*  This file author is Volker Boerchers
*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.features.format;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Vector;

import org.apache.commons.lang.StringUtils;
import org.freeplane.core.extension.IExtension;
import org.freeplane.core.resources.IFreeplanePropertyListener;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.TextUtils;
import org.freeplane.features.mode.Controller;
import org.freeplane.n3.nanoxml.IXMLParser;
import org.freeplane.n3.nanoxml.IXMLReader;
import org.freeplane.n3.nanoxml.StdXMLReader;
import org.freeplane.n3.nanoxml.XMLElement;
import org.freeplane.n3.nanoxml.XMLParserFactory;
import org.freeplane.n3.nanoxml.XMLWriter;

/**
* @author Volker Boerchers
*/
public class ScannerController implements IExtension, IFreeplanePropertyListener {
  private static final String SCANNER_XML = "scanner.xml";
  private static final String ROOT_ELEMENT = "scanners";
  private String pathToFile;
  private Scanner selectedScanner;
  private static List<Scanner> scanners = new ArrayList<Scanner>();
  private static boolean scannersLoaded;
 
  public ScannerController() {
    final String freeplaneUserDirectory = ResourceController.getResourceController().getFreeplaneUserDirectory();
    // applets have no user directory and no file access anyhow
    pathToFile = freeplaneUserDirectory == null ? null : freeplaneUserDirectory + File.separator + SCANNER_XML;
    initScanners();
    selectScanner(FormatUtils.getFormatLocaleFromResources());
        addParsersForStandardFormats();
        final ResourceController resourceController = ResourceController.getResourceController();
        resourceController.addPropertyChangeListener(this);
  }

  public static ScannerController getController() {
    return getController(Controller.getCurrentController());
  }

  public static ScannerController getController(Controller controller) {
    return (ScannerController) controller.getExtension(ScannerController.class);
  }
 
  public static void install(final ScannerController scannerController) {
    Controller.getCurrentController().addExtension(ScannerController.class, scannerController);
  }

  public void selectScanner(final Locale locale) {
    selectedScanner = findScanner(locale);
  }

  public Object parse(String string) {
    return selectedScanner.parse(string);
  }

  private Scanner findScanner(final Locale locale) {
    final String localeAsString = locale.toString();
    Scanner countryScanner = null;
    Scanner defaultScanner = null;
    for (Scanner scanner : scanners) {
      if (scanner.localeMatchesExactly(localeAsString))
        return scanner;
      else if (localeAsString.contains("_") && scanner.countryMatches(localeAsString))
        countryScanner = scanner;
      else if (scanner.isDefault())
        defaultScanner = scanner;
    }
    return countryScanner == null ? defaultScanner : countryScanner;
  }
 
  private Scanner findGoodMatch(final Locale locale) {
      final String localeAsString = locale.toString();
      Scanner countryScanner = null;
      for (Scanner scanner : scanners) {
          if (scanner.localeMatchesExactly(localeAsString))
              return scanner;
          else if (localeAsString.contains("_") && scanner.countryMatches(localeAsString))
              countryScanner = scanner;
      }
      return countryScanner;
  }

  private void initScanners() {
    if (scannersLoaded)
      return;
        scannersLoaded = true;
    try {
      if (pathToFile != null)
        loadScanners();
    }
        catch (final Exception e) {
      LogUtils.warn(e);
      UITools.errorMessage(TextUtils.getText("scanners_not_loaded"));
    }
    addAndSaveStandardScanners();
  }

    /** if standard formats wouldn't be parseable it would be difficult to edit recognized dates since the standard
     * format is used by the editor. */
    public void addParsersForStandardFormats() {
        final HashSet<String> patterns = new HashSet<String>();
        final List<Parser> parsers = selectedScanner.getParsers();
        for (Parser parser : parsers) {
            patterns.add(parser.getFormat());
        }
        final String standardDateFormat = FormatController.getController().getDefaultDateFormat().toPattern();
        if (!patterns.contains(standardDateFormat)) {
            selectedScanner.addParser(Parser.createParser(Parser.STYLE_DATE, IFormattedObject.TYPE_DATETIME,
                standardDateFormat, Locale.getDefault(), "STANDARD FORMAT"));
            LogUtils.info("added parsing support for standard date format " + standardDateFormat);
        }
        final String standardDateTimeFormat = FormatController.getController().getDefaultDateTimeFormat().toPattern();
        if (!patterns.contains(standardDateTimeFormat)) {
            selectedScanner.addParser(Parser.createParser(Parser.STYLE_DATE, IFormattedObject.TYPE_DATETIME,
                standardDateTimeFormat, Locale.getDefault(), "STANDARD FORMAT"));
            LogUtils.info("added parsing support for standard date time format " + standardDateTimeFormat);
        }
        // let's hope that for every locale a proper decimal number parser is defined.
    }

    private void addAndSaveStandardScanners() {
        final int originalCount = scanners.size();
        if (findGoodMatch(new Locale("en")) == null)
            scanners.add(createScanner_en());
        if (findGoodMatch(new Locale("de")) == null)
            scanners.add(createScanner_de());
        if (findGoodMatch(new Locale("hr")) == null)
            scanners.add(createScanner_hr());
        if (findGoodMatch(Locale.getDefault()) == null) {
            // "de_DE_WIN" -> "de_DE"
            final String shortLocale = Locale.getDefault().toString().replaceAll("(.*_.*)_.*", "$1");
            scanners.add(createScanner(new Locale(shortLocale)));
        }
        if (scanners.size() != originalCount)
            saveScannersNoThrow();
    }

    private Scanner createScanner_en() {
    final Scanner s = new Scanner(new String[] { "en" }, true);
    s.setFirstChars("+-0123456789.");
    final String tNumber = IFormattedObject.TYPE_NUMBER;
    final String tDate = IFormattedObject.TYPE_DATETIME;
    final Locale loc = new Locale("en");
    s.addParser(Parser.createParser(Parser.STYLE_DECIMAL, tNumber, null, loc, "supports locale specific numbers"));
    // number literals are a subset of english localized decimal parser
    // s.addParser(Parser.createParser(Parser.STYLE_NUMBERLITERAL, tNumber, null, loc, "numbers like 12345.12"));
    s.addParser(Parser.createParser(Parser.STYLE_ISODATE, tDate, null, loc, "ISO reader for date and date/time"));
    s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "M/d", loc, "completes date with current year"));
    s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "M/d/y", loc, "parses 4/21/11 or 4/21/2011"));
    s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "M/d/y H:m", loc, "parses datetime"));
    s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "M/d/y H:m:s", loc, "parses datetime"));
        s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "H:m", loc, "parses time, sets date to today"));
    return s;
  }

  private Scanner createScanner_de() {
    final Scanner s = new Scanner(new String[] { "de" }, false);
    s.setFirstChars("+-0123456789,.");
    final String tNumber = IFormattedObject.TYPE_NUMBER;
    final String tDate = IFormattedObject.TYPE_DATETIME;
    final Locale loc = new Locale("de");
    s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M", loc, "completes date with current year"));
    s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M.y", loc, "parses 21.4.11 or 21.4.2011"));
    s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M.y H:m", loc, "parses datetime"));
    s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M.y H:m:s", loc, "parses datetime"));
        s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "H:m", loc, "parses time, sets date to today"));
    s.addParser(Parser.createParser(Parser.STYLE_DECIMAL, tNumber, null, loc,
        "uses comma as decimal separator: 1.234,12"));
    s.addParser(Parser.createParser(Parser.STYLE_ISODATE, tDate, null, loc, "ISO reader for date and date/time"));
    s.addParser(Parser.createParser(Parser.STYLE_NUMBERLITERAL, tNumber, null, loc,
        "support dot as decimal separator (if nothing else matches)"));
    return s;
  }
 
  private Scanner createScanner_hr() {
      final Scanner s = new Scanner(new String[] { "hr" }, false);
      s.setFirstChars("+-0123456789,.");
      final String tNumber = IFormattedObject.TYPE_NUMBER;
      final String tDate = IFormattedObject.TYPE_DATETIME;
      final Locale loc = new Locale("hr");
      s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M", loc, "completes date with current year"));
      s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M.y", loc, "parses 21.4.11 or 21.4.2011"));
      s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M.y.", loc, "parses 21.4.11. or 21.4.2011."));
        s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M.y. H:m.", loc, "parses datetime"));
        s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "d.M.y. H:m:s", loc, "parses datetime"));
      s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, "H:m", loc, "parses time, sets date to today"));
      s.addParser(Parser.createParser(Parser.STYLE_DECIMAL, tNumber, null, loc,
              "uses comma as decimal separator: 1.234,12"));
      s.addParser(Parser.createParser(Parser.STYLE_ISODATE, tDate, null, loc, "ISO reader for date and date/time"));
      s.addParser(Parser.createParser(Parser.STYLE_NUMBERLITERAL, tNumber, null, loc,
              "support dot as decimal separator (if nothing else matches)"));
      return s;
  }

    private Scanner createScanner(Locale loc) {
        final Scanner s = new Scanner(new String[] { loc.toString() }, false);
        s.setFirstChars("+-0123456789,.");
        final String tNumber = IFormattedObject.TYPE_NUMBER;
        final String tDate = IFormattedObject.TYPE_DATETIME;
        final DateFormat shortDateTimeFormat = SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT,
            loc);
        if (shortDateTimeFormat instanceof SimpleDateFormat) {
            s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate,
                ((SimpleDateFormat) shortDateTimeFormat).toPattern(), loc, "short datetime format"));
        }
        final DateFormat shortDateFormat = SimpleDateFormat.getDateInstance(DateFormat.SHORT, loc);
        if (shortDateFormat instanceof SimpleDateFormat) {
            s.addParser(Parser.createParser(Parser.STYLE_DATE, tDate, ((SimpleDateFormat) shortDateFormat).toPattern(),
                loc, "short date format"));
        }
        s.addParser(Parser.createParser(Parser.STYLE_DECIMAL, tNumber, null, loc, "number format"));
        s.addParser(Parser.createParser(Parser.STYLE_ISODATE, tDate, null, loc, "ISO reader for date and date/time"));
        s.addParser(Parser.createParser(Parser.STYLE_NUMBERLITERAL, tNumber, null, loc,
            "support dot as decimal separator (if nothing else matches)"));
        return s;
    }

  void loadScanners() throws Exception {
    final File configXml = new File(pathToFile);
    if (!configXml.exists()) {
      LogUtils.info(pathToFile + " does not exist yet");
      return;
    }
    try {
      final IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
      final IXMLReader reader = new StdXMLReader(new BufferedInputStream(new FileInputStream(configXml)));
      parser.setReader(reader);
      final XMLElement loader = (XMLElement) parser.parse();
      final Vector<XMLElement> scannerElements = loader.getChildren();
      for (XMLElement elem : scannerElements) {
        scanners.add(parseScanner(elem));
      }
      boolean haveDefault = false;
      for (Scanner scanner : scanners) {
        if (scanner.isDefault()) {
          if (haveDefault)
            LogUtils.warn(configXml + ": multiple scanners are marked as default - fix that!");
          else
            haveDefault = true;
        }
      }
      if (!haveDefault)
        LogUtils.warn(configXml + ": no scanner is marked as default - fix that!");
    }
    catch (final IOException e) {
      LogUtils.warn("error parsing " + configXml, e);
    }
  }

  private Scanner parseScanner(XMLElement elem) {
    final String locales = elem.getAttribute("locale", "");
    final String isDefault = elem.getAttribute("default", "false");
    if (StringUtils.isEmpty(locales)) {
      throw new RuntimeException("wrong scanner in " + pathToFile
              + ": none of the following must be empty: locales=" + locales + ".");
    }
    final Scanner scanner = new Scanner(locales.trim().split(","), Boolean.parseBoolean(isDefault));
    final Locale locale = new Locale(scanner.getLocales().get(0));
    for (XMLElement child : elem.getChildren()) {
      if (child.getName().equals("checkfirstchar")) {
        final String chars = elem.getAttribute("chars", "");
        final boolean disabled = Boolean.parseBoolean(elem.getAttribute("disabled", "false"));
        if (!disabled)
          scanner.setFirstChars(chars);
      }
      else if (child.getName().equals("parser")) {
        scanner.addParser(parseParser(child, locale));
      }
    }
    return scanner;
  }

  private Parser parseParser(XMLElement elem, Locale locale) {
    final String type = elem.getAttribute("type", null);
    final String style = elem.getAttribute("style", null);
    final String format = elem.getAttribute("format", null);
    final String comment = elem.getAttribute("comment", null);
    return Parser.createParser(style, type, format, locale, comment);
  }

  private void saveScannersNoThrow() {
    try {
      saveScanners(scanners);
    }
        catch (final NoClassDefFoundError e) {
        }
        catch (final Exception e) {
            LogUtils.warn("cannot save create " + pathToFile, e);
        }
  }

  private void saveScanners(final List<Scanner> scanners) throws IOException {
    final XMLElement saver = new XMLElement();
    saver.setName(ROOT_ELEMENT);
    final String sep = System.getProperty("line.separator");
    final String description = commentLines("Description:" //
        , "" //
        , "<scanner> Scanners are locale dependent. If there is no scanner for" //
        , "the selected locale the scanner marked with default=\"true\" is choosen." //
        , " 'locales': A comma-separated list of locale names." //
        , "   The locale is selected via Preferences -> Environment -> Language" //
        , "   It's a pattern like 'en' (generic English) or 'en_US'" //
        , "   (English/USA). Use the more general two-letter form if appropriate." //
        , " 'default': Set to \"true\" for only one locale. The standard is 'en'." //
        , "" //
        , "<checkfirstchar> allows to enable a fast check for the first input" //
        , "character. If the first input character is not contained in the string" //
        , "given in attribute 'chars' no further attempts are made to parse the" //
        , "input as a number or date." //
        , "Do not use this option if you have have scanner formats that can" //
        , "recognize arbitrary text at the beginning of the pattern. To disable" //
        , "this check omit <checkfirstchar> or add the attribute disabled=\"true\"." //
        , " 'chars': A string of characters that may start data." //
        , "" //
        , "<type> selects the kind of data the scanner should recognize." //
        , " 'style' selects the formatter implementation:" //
        , "  - \"isodate\": flexible ISO date reader for strings like 2011-04-29 22:31:21" //
        , "    Only creates datetimes if time part is given, so no differentiation" //
        , "    between date and date/time is necessary." //
        , "  - \"date\": a special format for dates; needs attribute 'format'. See" //
        , "    http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html" //
        , "  - \"numberliteral\": parses Java float or integral number literals only, with" //
        , "    a dot as decimal separator and no thousands separator. See" //
        , "    http://en.wikibooks.org/wiki/Java_Programming/Literals/Numeric_Literals/Floating_Point_Literals" //
        , "  - \"decimal\": a special format for numbers; needs attribute 'format'. See" //
        , "    http://download.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html" //
        , " 'format': The format code of a \"date\" or \"decimal\" scanner." //
        , " 'comment': Inline comment, not used by the application.");
    final String header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + sep + description;
    for (Scanner scanner : scanners) {
      saver.addChild(scanner.toXml());
    }
    final Writer writer = new FileWriter(pathToFile);
    final XMLWriter xmlWriter = new XMLWriter(writer);
    xmlWriter.addRawContent(header);
    xmlWriter.write(saver, true);
    writer.close();
  }

  private String commentLines(String... comments) {
    StringBuilder builder = new StringBuilder(comments.length * 100);
    for (String comment : comments) {
      builder.append(String.format("<!-- %-71s -->%n", comment));
    }
    return builder.toString();
  }

    public void propertyChanged(String propertyName, String newValue, String oldValue) {
        if (FormatUtils.equalsFormatLocaleName(propertyName)) {
            selectScanner(FormatUtils.getFormatLocaleFromResources());
        }
    }
}
TOP

Related Classes of org.freeplane.features.format.ScannerController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.