/*
* 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.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Formatter;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.TypeReference;
import org.freeplane.n3.nanoxml.XMLElement;
/** a thin wrapper around {@link SimpleDateFormat}, {@link DecimalFormat} and {@link Formatter}.
* <p>
* Parsing is not supported! */
public abstract class PatternFormat /*extends Format*/ {
private static final String SERIALIZATION_SEPARATOR = ":";
public static final String IDENTITY_PATTERN = "NO_FORMAT";
public static final String STANDARD_FORMAT_PATTERN = "STANDARD_FORMAT";
private static final PatternFormat IDENTITY = new IdentityPatternFormat();
private static final PatternFormat STANDARD = new StandardPatternFormat();
static final String STYLE_FORMATTER = "formatter";
static final String STYLE_DATE = "date";
static final String STYLE_DECIMAL = "decimal";
static final String TYPE_IDENTITY = "identity";
static final String TYPE_STANDARD = "standard";
private static final String ELEMENT_NAME = "format";
private final String type;
private final String pattern;
private String name;
private Locale locale;
public PatternFormat(String pattern, String type) {
this.type = type;
this.pattern = pattern;
}
/** the formal format description. */
public String getPattern() {
return pattern;
}
/** selects the kind of data the formatter is intended to format. */
public String getType() {
return type;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
/** selects the formatter implementation, e.g. "formatter" or "date" */
public abstract String getStyle();
public static PatternFormat create(final String pattern, final String style, final String type) {
if (pattern.equals(IDENTITY_PATTERN))
return new IdentityPatternFormat();
else if (pattern.equals(STANDARD_FORMAT_PATTERN))
return new StandardPatternFormat();
else if (style.equals(STYLE_DATE))
return new DatePatternFormat(pattern);
else if (style.equals(STYLE_FORMATTER))
return new FormatterPatternFormat(pattern, type);
else if (style.equals(STYLE_DECIMAL))
return new DecimalPatternFormat(pattern);
else
throw new IllegalArgumentException("unknown format style");
}
public static PatternFormat create(final String pattern, final String style, final String type,
final String name) {
final PatternFormat format = create(pattern, style, type);
format.setName(name);
return format;
}
public static PatternFormat create(final String pattern, final String style, final String type,
final String name, final Locale locale) {
final PatternFormat format = create(pattern, style, type, name);
format.setLocale(locale);
return format;
}
// yyyy-MM-dd HH:mm:ss
final static Pattern datePattern = Pattern.compile("yy|[Hh]{1,2}:mm");
// %[argument_index$] [flags] [width] conversion
// == conversions
// ignore boolean: bB
// ignore hash: hH
// sS
// ignore char: cC
// number: doxXeEfgGaA
// ignore literals: %n
// time prefix: tT
final static Pattern formatterPattern = Pattern.compile("%" //
// + "(?:[\\d<]+\\$)?" // Freeplane: no support for argument index$!
+ "(?:[-#+ 0,(]+)?" // flags
+ "(?:[\\d.]+)?" // width
+ "([sSdoxXeEfgGaA]|[tT][HIklMSLNpzZsQBbhAaCYyjmdeRTrDFc])"); // conversion
// this method is null safe
public static PatternFormat guessPatternFormat(final String pattern) {
try {
if (pattern == null || pattern.length() == 0)
return null;
final Matcher matcher = formatterPattern.matcher(pattern);
if (matcher.find()) {
// System.err.println("pattern='" + pattern + "' match='" + matcher.group() + "'");
final char conversion = matcher.group(1).charAt(0);
if (matcher.find()) {
LogUtils.warn("found multiple formats in this formatter pattern: '" + pattern + "'");
return null;
}
switch (conversion) {
case 's':
case 'S':
return new FormatterPatternFormat(pattern, IFormattedObject.TYPE_STRING);
case 'd':
case 'o':
case 'x':
case 'X':
case 'e':
case 'E':
case 'f':
case 'g':
case 'G':
case 'a':
case 'A':
return new FormatterPatternFormat(pattern, IFormattedObject.TYPE_NUMBER);
case 't':
case 'T':
return new FormatterPatternFormat(pattern, IFormattedObject.TYPE_DATE);
}
}
if (datePattern.matcher(pattern).find()) {
return new DatePatternFormat(pattern);
}
if (pattern.indexOf('#') != -1 || pattern.indexOf('0') != -1) {
return new DecimalPatternFormat(pattern);
}
// only as a last resort?!
if (pattern.equals(IDENTITY_PATTERN)) {
return IDENTITY;
}
if (pattern.equals(STANDARD_FORMAT_PATTERN)) {
return STANDARD;
}
LogUtils.warn("not a pattern format: '" + pattern + "'");
return null;
}
catch (Exception e) {
LogUtils.warn("can't build a formatter for this pattern '" + pattern + "'", e);
return null;
}
}
public static PatternFormat getIdentityPatternFormat() {
return IDENTITY;
}
public static PatternFormat getStandardPatternFormat() {
return STANDARD;
}
public XMLElement toXml() {
final XMLElement xmlElement = new XMLElement(ELEMENT_NAME);
xmlElement.setAttribute("type", getType());
xmlElement.setAttribute("style", getStyle());
if (getName() != null)
xmlElement.setAttribute("name", getName());
if (getLocale() != null)
xmlElement.setAttribute("locale", getLocale().toString());
xmlElement.setContent(getPattern());
return xmlElement;
}
public String serialize() {
return getType() + SERIALIZATION_SEPARATOR + getStyle() + SERIALIZATION_SEPARATOR + TypeReference.encode(getPattern());
}
public static PatternFormat deserialize(String string) {
final String[] tokens = string.split(SERIALIZATION_SEPARATOR, 3);
return create(TypeReference.decode(tokens[2]), tokens[1], tokens[0]);
}
public boolean acceptsDate() {
return getType().equals(IFormattedObject.TYPE_DATE) || getPattern().equals(STANDARD_FORMAT_PATTERN);
}
public boolean acceptsNumber() {
return getType().equals(IFormattedObject.TYPE_NUMBER) || getPattern().equals(STANDARD_FORMAT_PATTERN);
}
public boolean acceptsString() {
return getType().equals(IFormattedObject.TYPE_STRING) || getPattern().equals(STANDARD_FORMAT_PATTERN);
}
abstract public Object formatObject(Object toFormat);
@Override
public int hashCode() {
final String style = getStyle();
final int prime = 31;
int result = 1;
result = prime * result + ((pattern == null) ? 0 : pattern.hashCode());
result = prime * result + ((style == null) ? 0 : style.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PatternFormat other = (PatternFormat) obj;
if (pattern == null) {
if (other.pattern != null)
return false;
}
else if (!pattern.equals(other.pattern))
return false;
final String style = getStyle();
if (style == null) {
if (other.getStyle() != null)
return false;
}
else if (!style.equals(other.getStyle()))
return false;
if (type == null) {
if (other.type != null)
return false;
}
else if (!type.equals(other.type))
return false;
return true;
}
@Override
public String toString() {
return pattern;
}
}