Package org.freeplane.features.format

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

/*
*  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.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
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.resources.components.IValidator;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.core.util.FileUtils;
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 FormatController implements IExtension, IFreeplanePropertyListener {
  private static final String RESOURCES_NUMBER_FORMAT = "number_format";
  private static final String RESOURCES_DATETIME_FORMAT = "datetime_format";
  private static final String RESOURCES_DATE_FORMAT = "date_format";
  private static final String FORMATS_XML = "formats.xml";
  private static final String ROOT_ELEMENT = "formats";
  private String pathToFile;
  private Locale locale;
  private List<PatternFormat> specialFormats = new ArrayList<PatternFormat>();
  private List<PatternFormat> dateFormats = new ArrayList<PatternFormat>();
  private List<PatternFormat> numberFormats = new ArrayList<PatternFormat>();
  private List<PatternFormat> stringFormats = new ArrayList<PatternFormat>();
  private boolean formatsLoaded;
  private SimpleDateFormat defaultDateFormat;
  private SimpleDateFormat defaultDateTimeFormat;
  private HashMap<String, SimpleDateFormat> dateFormatCache = new HashMap<String, SimpleDateFormat>();
  private DecimalFormat defaultNumberFormat;
  private HashMap<String, DecimalFormat> numberFormatCache = new HashMap<String, DecimalFormat>();
    static private boolean firstError = true;

  public IValidator createValidator (){
      return new IValidator() {
      public ValidationResult validate(Properties properties) {
        final ValidationResult result = new ValidationResult();
        try {
                    createDateFormat(properties.getProperty(RESOURCES_DATE_FORMAT));
                    if (properties.getProperty(RESOURCES_DATE_FORMAT).isEmpty())
                        throw new Exception();
                }
                catch (Exception e) {
                  result.addError(TextUtils.getText("OptionPanel.validate_invalid_date_format"));
                }
                try {
                    createDefaultDateTimeFormat(properties.getProperty(RESOURCES_DATETIME_FORMAT));
                    if (properties.getProperty(RESOURCES_DATETIME_FORMAT).isEmpty())
                        throw new Exception();
                }
                catch (Exception e) {
                  result.addError(TextUtils.getText("OptionPanel.validate_invalid_datetime_format"));
                }
                try {
                    getDecimalFormat(properties.getProperty(RESOURCES_NUMBER_FORMAT));
                    if (properties.getProperty(RESOURCES_NUMBER_FORMAT).isEmpty())
                        throw new Exception();
                }
                catch (Exception e) {
                  result.addError(TextUtils.getText("OptionPanel.validate_invalid_number_format"));
                }
        return result;
      }
    };
  }

  public FormatController() {
    final String freeplaneUserDirectory = ResourceController.getResourceController().getFreeplaneUserDirectory();
    // applets have no user directory and no file access anyhow
    pathToFile = freeplaneUserDirectory == null ? null : freeplaneUserDirectory + File.separator + FORMATS_XML;
    locale = FormatUtils.getFormatLocaleFromResources();
    initPatternFormats();
        final ResourceController resourceController = ResourceController.getResourceController();
        resourceController.addPropertyChangeListener(this);
  }

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

  public static FormatController getController(Controller controller) {
    return (FormatController) controller.getExtension(FormatController.class);
  }
 
  public static void install(final FormatController formatController) {
    Controller.getCurrentController().addExtension(FormatController.class, formatController);
    Controller.getCurrentController().addOptionValidator(formatController.createValidator());
  }

  private void initPatternFormats() {
    if (formatsLoaded)
      return;
    specialFormats.add(PatternFormat.getStandardPatternFormat());
    specialFormats.add(PatternFormat.getIdentityPatternFormat());
    try {
      if (pathToFile != null)
        loadFormats();
    }
    catch (final Exception e) {
      LogUtils.warn(e);
      if(firstError){
          firstError = false;
          UITools.errorMessage(TextUtils.getText("formats_not_loaded"));
      }
    }
        if (numberFormats.isEmpty() && dateFormats.isEmpty() && stringFormats.isEmpty()) {
            addStandardFormats();
            if (pathToFile != null)
                saveFormatsNoThrow();
        }
        formatsLoaded = true;
  }

  private void addStandardFormats() {
    String number = IFormattedObject.TYPE_NUMBER;
    numberFormats.add(createFormat("#0.####", PatternFormat.STYLE_DECIMAL, number,
        "default number", locale));
    numberFormats.add(createFormat("#.00", PatternFormat.STYLE_DECIMAL, number, "decimal", locale));
    numberFormats.add(createFormat("#", PatternFormat.STYLE_DECIMAL, number, "integer", locale));
    numberFormats.add(createFormat("#.##%", PatternFormat.STYLE_DECIMAL, number, "percent", locale));
    String dType = IFormattedObject.TYPE_DATE;
    final String dStyle = PatternFormat.STYLE_DATE;
    dateFormats.add(createLocalPattern("short date", SimpleDateFormat.SHORT, null));
    dateFormats.add(createLocalPattern("medium date", SimpleDateFormat.MEDIUM, null));
    dateFormats.add(createLocalPattern("short datetime", SimpleDateFormat.SHORT, SimpleDateFormat.SHORT));
    dateFormats.add(createLocalPattern("medium datetime", SimpleDateFormat.MEDIUM, SimpleDateFormat.SHORT));
    dateFormats.add(createFormat("yyyy-MM-dd", dStyle, dType, "short iso date", locale));
    dateFormats.add(createFormat("yyyy-MM-dd HH:mm", dStyle, dType, "long iso date", locale));
    dateFormats.add(createFormat(FormattedDate.ISO_DATE_TIME_FORMAT_PATTERN, dStyle, dType,
        "full iso date", locale));
    dateFormats.add(createFormat("HH:mm", dStyle, dType, "time", locale));
  }

  private PatternFormat createLocalPattern(String name, int dateStyle, Integer timeStyle) {
    final SimpleDateFormat simpleDateFormat = (SimpleDateFormat) (timeStyle == null ? SimpleDateFormat
        .getDateInstance(dateStyle, locale) : SimpleDateFormat.getDateTimeInstance(dateStyle, timeStyle, locale));
    final String dStyle = PatternFormat.STYLE_DATE;
    final String dType = IFormattedObject.TYPE_DATE;
    return createFormat(simpleDateFormat.toPattern(), dStyle, dType, name, locale);
  }

  private void loadFormats() throws Exception {
    BufferedInputStream inputStream = null;
    final File configXml = new File(pathToFile);
    if (!configXml.exists()) {
      LogUtils.info(pathToFile + " does not exist yet");
      return;
    }
    try {
      final IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
      inputStream = new BufferedInputStream(new FileInputStream(configXml));
      final IXMLReader reader = new StdXMLReader(inputStream);
      parser.setReader(reader);
      final XMLElement loader = (XMLElement) parser.parse();
      final Vector<XMLElement> formats = loader.getChildren();
      for (XMLElement elem : formats) {
        final String type = elem.getAttribute("type", null);
        final String style = elem.getAttribute("style", null);
        final String name = elem.getAttribute("name", null);
        final String locale = elem.getAttribute("locale", null);
        final String content = elem.getContent();
        if (StringUtils.isEmpty(type) || StringUtils.isEmpty(style) || StringUtils.isEmpty(content)) {
          throw new RuntimeException("wrong format in " + configXml
                  + ": none of the following must be empty: type=" + type + ", style=" + style
                  + ", element content=" + content);
        }
        else {
          final PatternFormat format = createFormat(content, style, type, name,
              (locale == null ? null : new Locale(locale)));
          if (type.equals(IFormattedObject.TYPE_DATE)) {
            dateFormats.add(format);
          }
          else if (type.equals(IFormattedObject.TYPE_NUMBER)) {
            numberFormats.add(format);
          }
          else if (type.equals(IFormattedObject.TYPE_STRING)) {
            stringFormats.add(format);
          }
          else if (type.equals(PatternFormat.TYPE_STANDARD)) {
              // ignore this pattern that crept in in some 1.2 beta version
          }
          else {
            throw new RuntimeException("unknown type in " + configXml + ": type=" + type + ", style="
                    + style + ", element content=" + content);
          }
        }
      }
    }
    catch (final IOException e) {
      LogUtils.warn("error parsing " + configXml, e);
    }
    finally {
      FileUtils.silentlyClose(inputStream);
    }
  }

  private void saveFormatsNoThrow() {
    try {
      saveFormats(getAllFormats());
    }
    catch (final Exception e) {
      LogUtils.warn("cannot create " + pathToFile, e);
    }
  }

  public void addPatternFormat(PatternFormat format){
    specialFormats.add(format);
  }
  public ArrayList<PatternFormat> getAllFormats() {
    final ArrayList<PatternFormat> formats = new ArrayList<PatternFormat>();
    formats.addAll(specialFormats);
    formats.addAll(numberFormats);
    formats.addAll(dateFormats);
    formats.addAll(stringFormats);
    return formats;
  }

  private void saveFormats(final List<PatternFormat> formats) throws IOException {
    final XMLElement saver = new XMLElement();
    saver.setName(ROOT_ELEMENT);
    final String sep = System.getProperty("line.separator");
    final String header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
            + sep //
            + "<!-- 'type' selects the kind of data the formatter is intended to format. -->"
            + sep //
            + "<!-- 'style' selects the formatter implementation: -->"
            + sep //
            + "<!--   - 'date': http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html -->"
            + sep //
            + "<!--   - 'decimal': http://download.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html -->"
            + sep //
            + "<!--   - 'formatter': http://download.oracle.com/javase/6/docs/api/java/util/Formatter.html -->"
            + sep //
            + "<!--   - 'name': a informal name, a comment that's not visible in the app -->" + sep //
            + "<!--   - 'locale': the name of the locale, only set for locale dependent format codes -->" + sep;
    for (PatternFormat patternFormat : formats) {
            if (!patternFormat.getType().equals(PatternFormat.TYPE_IDENTITY)
                    && !patternFormat.getType().equals(PatternFormat.TYPE_STANDARD)) {
                saver.addChild(patternFormat.toXml());
            }
    }
    final Writer writer = new FileWriter(pathToFile);
    final XMLWriter xmlWriter = new XMLWriter(writer);
    xmlWriter.addRawContent(header);
    xmlWriter.write(saver, true);
    writer.close();
  }

  public List<PatternFormat> getDateFormats() {
    return dateFormats;
  }

  public List<PatternFormat> getNumberFormats() {
    return numberFormats;
  }

  public List<PatternFormat> getStringFormats() {
    return stringFormats;
  }

  /** @deprecated use getAllFormats() instead. */
  public List<String> getAllPatterns() {
    final ArrayList<PatternFormat> formats = getAllFormats();
    ArrayList<String> result = new ArrayList<String>(formats.size());
    for (PatternFormat patternFormat : formats) {
      result.add(patternFormat.getPattern());
    }
    return result;
  }

  /** returns a matching {@link IFormattedObject} if possible and if <a>formatString</a> is not-null.
   * Otherwise <a>defaultObject</a> is returned.
   * Removes format if <a>formatString</a> is null. */
  public static Object format(final Object obj, final String formatString, final Object defaultObject) {
    try {
      final PatternFormat format = PatternFormat.guessPatternFormat(formatString);
      // logging for invalid pattern is done in guessPatternFormat()
            if (obj == null)
                return obj;
      Object toFormat = extractObject(obj);
      if (format == null)
        return toFormat;
      if (toFormat instanceof String) {
          final String string = (String) toFormat;
                if (string.startsWith("=")) {
              return new FormattedFormula(string, formatString);
          }
          else {
                final ScannerController scannerController = ScannerController.getController();
                if (scannerController != null)
                    toFormat = scannerController.parse(string);
          }
            }
            if (format.acceptsDate() && toFormat instanceof Date) {
        return new FormattedDate((Date) toFormat, formatString);
      }
            else if (format.acceptsNumber() && toFormat instanceof Number) {
                return new FormattedNumber((Number) toFormat, formatString, format.formatObject(toFormat).toString());
            }
            else {
        return new FormattedObject(toFormat, format);
      }
    }
    catch (Exception e) {
            // Be quiet, just like Excel does...
            // LogUtils.warn("cannot format '" + StringUtils.abbreviate(obj.toString(), 20) + "' of type "
            //               + obj.getClass().getSimpleName() + " with " + formatString + ": " + e.getMessage());
      return defaultObject;
    }
  }

    private static Object extractObject(final Object obj) {
        return (obj instanceof IFormattedObject) ? ((IFormattedObject) obj).getObject() : obj;
    }

    /** returns a matching IFormattedObject if possible and if formatString is not-null.
     * Otherwise <a>obj</a> is returned.
     * Removes format if formatString is null. */
    public static Object format(final Object obj, final String formatString) {
        return format(obj, formatString, obj);
    }

    public static Object formatUsingDefault(final Object object) {
        if (object instanceof Date)
            return format(object, FormatController.getController().getDefaultDateTimeFormat().toPattern());
        if (object instanceof Number)
            return format(object, FormatController.getController().getDefaultNumberFormat().toPattern());
        return object;
    }

  public Format getDefaultFormat(String type) {
    if (type.equals(IFormattedObject.TYPE_DATE))
      return getDefaultDateFormat();
    else if (type.equals(IFormattedObject.TYPE_DATETIME))
      return getDefaultDateTimeFormat();
    else if (type.equals(IFormattedObject.TYPE_NUMBER))
      return getDefaultNumberFormat();
    else
      throw new IllegalArgumentException("unknown format style");
  }

  public SimpleDateFormat getDefaultDateFormat() {
    if (defaultDateFormat != null)
      return defaultDateFormat;
    final ResourceController resourceController = ResourceController.getResourceController();

    // DateFormatParser cannot handle empty date format!
    fixEmptyDataFormatProperty(resourceController, RESOURCES_DATE_FORMAT, "SHORT");

    String datePattern = resourceController.getProperty(RESOURCES_DATE_FORMAT);
    defaultDateFormat = createDateFormat(datePattern);
    return defaultDateFormat;
  }

  /**
   * Fix old invalid values (empty data format properties) on startup.
   * For new configurations, this is forced by the Validator on top of this file!
   * @param resourceController
   * @param resourceProperty
   * @param defaultValue
   */
  private void fixEmptyDataFormatProperty(final ResourceController resourceController,
      final String resourceProperty, final String defaultValue)
  {
    if (resourceController.getProperty(resourceProperty).isEmpty()) {
      resourceController.setProperty(resourceProperty, defaultValue);
    }
  }

  private static SimpleDateFormat createDateFormat(final String datePattern) {
    final Integer style = getDateStyle(datePattern);
    if (style != null)
      return (SimpleDateFormat) DateFormat.getDateInstance(style, FormatUtils.getFormatLocaleFromResources());
    else
      return new SimpleDateFormat(datePattern, FormatUtils.getFormatLocaleFromResources());
  }

  public SimpleDateFormat getDefaultDateTimeFormat() {
    if (defaultDateTimeFormat != null)
      return defaultDateTimeFormat;
    final ResourceController resourceController = ResourceController.getResourceController();

    // DateFormatParser cannot handle empty date format!
    fixEmptyDataFormatProperty(resourceController, RESOURCES_DATETIME_FORMAT, "SHORT,SHORT");

    String datetimePattern = resourceController.getProperty(RESOURCES_DATETIME_FORMAT);
    defaultDateTimeFormat = createDefaultDateTimeFormat(datetimePattern);
    return defaultDateTimeFormat;
  }

  private SimpleDateFormat createDefaultDateTimeFormat(String datetimePattern) {
    final String[] styles = datetimePattern.split("\\s*,\\s*");
    if (styles.length == 2 && getDateStyle(styles[0]) != null && getDateStyle(styles[1]) != null)
      return (SimpleDateFormat) DateFormat.getDateTimeInstance(getDateStyle(styles[0]), getDateStyle(styles[1]),
          FormatUtils.getFormatLocaleFromResources());
    else
      return getDateFormat(datetimePattern);
  }

  private static Integer getDateStyle(final String string) {
    if (string.equals("SHORT"))
      return DateFormat.SHORT;
    if (string.equals("MEDIUM"))
      return DateFormat.MEDIUM;
    if (string.equals("LONG"))
      return DateFormat.LONG;
    if (string.equals("FULL"))
      return DateFormat.FULL;
    return null;
  }

  public DecimalFormat getDefaultNumberFormat() {
    if (defaultNumberFormat != null)
      return defaultNumberFormat;
      final ResourceController resourceController = ResourceController.getResourceController();

    // an empty number format does not make sense!
    fixEmptyDataFormatProperty(resourceController, RESOURCES_NUMBER_FORMAT, "#0.####");

    defaultNumberFormat = getDecimalFormat(resourceController.getProperty(RESOURCES_NUMBER_FORMAT));
      return defaultNumberFormat;
    }

  /** @param pattern either a string (see {@link DecimalFormat}) or null for a default formatter. */
  public DecimalFormat getDecimalFormat(final String pattern) {
    DecimalFormat format = numberFormatCache.get(pattern);
    if (format == null) {
      format = (DecimalFormat) ((pattern == null) ? getDefaultNumberFormat()
              : new DecimalFormat(pattern, new DecimalFormatSymbols(FormatUtils.getFormatLocaleFromResources())));
      numberFormatCache.put(pattern, format);
    }
    return format;
  }

  public SimpleDateFormat getDateFormat(String pattern) {
      SimpleDateFormat parser = dateFormatCache.get(pattern);
        if (parser == null) {
          parser = new SimpleDateFormat(pattern, FormatUtils.getFormatLocaleFromResources());
          dateFormatCache.put(pattern, parser);
        }
      return parser;
    }

    public void propertyChanged(String propertyName, String newValue, String oldValue) {
        if (propertyName.equals(RESOURCES_DATE_FORMAT)) {
            defaultDateFormat = createDateFormat(newValue);
            final ScannerController scannerController = ScannerController.getController();
            if (scannerController != null)
                scannerController.addParsersForStandardFormats();
        }
        else if (propertyName.equals(RESOURCES_DATETIME_FORMAT)) {
            defaultDateTimeFormat = createDefaultDateTimeFormat(newValue);
            final ScannerController scannerController = ScannerController.getController();
            if (scannerController != null)
                scannerController.addParsersForStandardFormats();
        }
        else if (propertyName.equals(RESOURCES_NUMBER_FORMAT)) {
            defaultNumberFormat = getDecimalFormat(newValue);
        }
        else if (FormatUtils.equalsFormatLocaleName(propertyName)) {
            locale = FormatUtils.getFormatLocaleFromResources();
        }
    }

  public List<PatternFormat> getSpecialFormats() {
    return specialFormats;
  }

  public PatternFormat createFormat(String pattern, String style, String type) {
    for(PatternFormat specialFormat : specialFormats)
      if (pattern.equals(specialFormat.getPattern()))
        return specialFormat;
      if (style.equals(PatternFormat.STYLE_DATE))
      return new DatePatternFormat(pattern);
    else if (style.equals(PatternFormat.STYLE_FORMATTER))
      return new FormatterPatternFormat(pattern, type);
    else if (style.equals(PatternFormat.STYLE_DECIMAL))
      return new DecimalPatternFormat(pattern);
    else
      throw new IllegalArgumentException("unknown format style");
  }

  public PatternFormat createFormat(final String pattern, final String style, final String type,
                                                  final String name, final Locale locale) {
    final PatternFormat format = createFormat(pattern, style, type, name);
    format.setLocale(locale);
    return format;
  }

  public PatternFormat createFormat(final String pattern, final String style, final String type,
                                                  final String name) {
    final PatternFormat format = createFormat(pattern, style, type);
    format.setName(name);
    return format;
  }

}
TOP

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

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.