package name.abuchen.portfolio.model; import java.text.DecimalFormat; import java.text.MessageFormat; import java.text.NumberFormat; import java.text.ParseException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.format.FormatStyle; import java.util.Comparator; import java.util.regex.Matcher; import java.util.regex.Pattern; import name.abuchen.portfolio.Messages; import name.abuchen.portfolio.money.Values; public class AttributeType { private static final Pattern PATTERN = Pattern.compile("^([\\d.]*)(,(\\d*))?$"); //$NON-NLS-1$ public interface Converter { String toString(Object object); Object fromString(String value); } public static class StringConverter implements Converter { @Override public String toString(Object object) { return object != null ? (String) object : ""; //$NON-NLS-1$ } @Override public Object fromString(String value) { return value.trim().length() > 0 ? value.trim() : null; } } private static class LongConverter implements Converter { private final NumberFormat full = new DecimalFormat("#,###"); //$NON-NLS-1$ private Values<Long> values; public LongConverter(Values<Long> values) { this.values = values; } @Override public String toString(Object object) { return object != null ? values.format((Long) object) : ""; //$NON-NLS-1$ } @Override public Object fromString(String value) { try { if (value.trim().length() == 0) return null; Matcher m = PATTERN.matcher(value); if (!m.matches()) throw new IllegalArgumentException(MessageFormat.format(Messages.MsgNotANumber, value)); String strBefore = m.group(1); Number before = strBefore.trim().length() > 0 ? full.parse(strBefore) : Long.valueOf(0); String strAfter = m.group(3); int after = 0; if (strAfter != null && strAfter.length() > 0) { after = Integer.parseInt(strAfter); int length = (int) Math.log10(values.factor()); for (int ii = strAfter.length(); ii > length; ii--) after /= 10; for (int ii = strAfter.length(); ii < length; ii++) after *= 10; } long resultValue = before.longValue() * (int) values.factor() + after; return Long.valueOf(resultValue); } catch (ParseException e) { throw new IllegalArgumentException(e); } } } public static class AmountConverter extends LongConverter { public AmountConverter() { super(Values.Amount); } } public static class AmountPlainConverter extends LongConverter { public AmountPlainConverter() { super(Values.AmountPlain); } } public static class QuoteConverter extends LongConverter { public QuoteConverter() { super(Values.Quote); } } public static class ShareConverter extends LongConverter { public ShareConverter() { super(Values.Share); } } private static class DoubleConverter implements Converter { private final NumberFormat full = new DecimalFormat("#,###.##"); //$NON-NLS-1$ private Values<Double> values; public DoubleConverter(Values<Double> values) { this.values = values; } @Override public String toString(Object object) { return object != null ? values.format((Double) object) : ""; //$NON-NLS-1$ } @Override public Object fromString(String value) { try { if (value.trim().length() == 0) return null; Matcher m = PATTERN.matcher(value); if (!m.matches()) throw new IllegalArgumentException(MessageFormat.format(Messages.MsgNotANumber, value)); return Double.valueOf(full.parse(value).doubleValue()); } catch (ParseException e) { throw new IllegalArgumentException(e); } } } public static class PercentPlainConverter extends DoubleConverter { public PercentPlainConverter() { super(Values.PercentPlain); } } public static class DateConverter implements Converter { private static final DateTimeFormatter[] formatters = new DateTimeFormatter[] { DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM), DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT), // DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG), // DateTimeFormatter.ofPattern("d.M.yyyy"), //$NON-NLS-1$ DateTimeFormatter.ofPattern("d.M.yy"), //$NON-NLS-1$ DateTimeFormatter.ISO_DATE }; @Override public String toString(Object object) { if (object != null) return Values.Date.format((LocalDate) object); else return ""; //$NON-NLS-1$ } @Override public Object fromString(String value) { if (value.trim().length() == 0) return null; for (DateTimeFormatter formatter : formatters) { try { return LocalDate.parse(value, formatter); } catch (DateTimeParseException ignore) { // continue with next formatter } } throw new IllegalArgumentException(MessageFormat.format(Messages.MsgErrorNotAValidDate, value)); } } private final String id; private String name; private String columnLabel; private Class<? extends Attributable> target; private Class<?> type; /** * Converter. Do not persist (includes formats, etc.) but recreate out of * type and value parameters. */ private transient Converter converter; private String converterClass; public AttributeType(String id) { this.id = id; } public String getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColumnLabel() { return columnLabel; } public void setColumnLabel(String columnLabel) { this.columnLabel = columnLabel; } public Class<?> getType() { return type; } public void setType(Class<?> type) { this.type = type; } public Class<? extends Attributable> getTarget() { return target; } public void setTarget(Class<? extends Attributable> target) { this.target = target; } public boolean supports(Class<? extends Attributable> type) { return target != null ? target.isAssignableFrom(type) : true; } public void setConverter(Class<? extends Converter> converterClass) { this.converterClass = converterClass.getName(); this.converter = null; // in case it was used before } public Converter getConverter() { try { if (converter == null) converter = (Converter) Class.forName(converterClass).newInstance(); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { throw new RuntimeException(e); } return converter; } public boolean isNumber() { return Number.class.isAssignableFrom(type); } public Comparator<Object> getComparator() { return new Comparator<Object>() { @SuppressWarnings("unchecked") @Override public int compare(Object o1, Object o2) { if (o1 == null && o2 == null) return 0; else if (o1 == null) return -1; else if (o2 == null) return 1; if (type == Long.class) return ((Long) o1).compareTo((Long) o2); else if (type == Double.class) return ((Double) o1).compareTo((Double) o2); else if (type == String.class) return ((String) o1).compareToIgnoreCase((String) o2); else return ((Comparable<Object>) o1).compareTo((Comparable<Object>) o2); } }; } }