/*
* ARX: Powerful Data Anonymization
* Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.deidentifier.arx;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.deidentifier.arx.aggregates.AggregateFunction;
import org.deidentifier.arx.aggregates.AggregateFunction.AggregateFunctionBuilder;
/**
* This class provides access to the data types supported by the ARX framework.
*
* @author Fabian Prasser
* @author Florian Kohlmayer
* @param <T>
*/
public abstract class DataType<T> implements Serializable, Comparator<T> {
/**
* Base class for date/time types.
*
* @author Fabian Prasser
*/
public static class ARXDate extends DataType<Date> implements DataTypeWithFormat, DataTypeWithRatioScale<Date> {
/** SVUID */
private static final long serialVersionUID = -1658470914184442833L;
/** The description of the data type. */
// TODO: This is bogus. Date has an ordinal scale
private static final DataTypeDescription<Date> description = new DataTypeDescription<Date>(Date.class, "Date/Time", DataScale.INTERVAL, true, listDateFormats()){
private static final long serialVersionUID = -1723392257250720908L;
@Override public DataType<Date> newInstance() { return DATE; }
@Override public DataType<Date> newInstance(String format) {return createDate(format);}
@Override public DataType<Date> newInstance(String format, Locale locale) {return createDate(format, locale);}
};
/** Format. */
private final SimpleDateFormat format;
/** Format string. */
private final String string;
/** Locale. */
private final Locale locale;
/**
* Create a date with a "dd.MM.yyyy" format string
* for <code>SimpleDateFormat</code> and default locale.
* @see <a href="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html">SimpleDateFormat</a>
*/
private ARXDate() {
this("Default");
}
/**
* Create a date with a format string and the default locale. Format strings must be valid formats
* for <code>SimpleDateFormat</code>.
*
* @param formatString
* @see <a href="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html">SimpleDateFormat</a>
*/
private ARXDate(final String formatString) {
if (formatString == null || formatString.equals("Default")) {
this.string = "dd.MM.yyyy";
this.format = new SimpleDateFormat(string);
this.locale = null;
} else {
this.format = new SimpleDateFormat(formatString);
this.string = formatString;
this.locale = null;
}
}
/**
* Create a date with a format string and a given locale. Format strings must be valid formats
* for <code>SimpleDateFormat</code>.
*
* @param formatString
* @param locale
* @see <a href="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html">SimpleDateFormat</a>
*/
private ARXDate(String formatString, Locale locale) {
if (formatString == null || formatString.equals("Default")) {
this.string = "dd.MM.yyyy";
this.format = new SimpleDateFormat(string, locale);
this.locale = locale;
} else {
this.format = new SimpleDateFormat(formatString, locale);
this.string = formatString;
this.locale = locale;
}
}
@Override
public Date add(Date augend, Date addend) {
long d1 = augend.getTime();
long d2 = addend.getTime();
return new Date(d1 + d2);
}
@Override
public DataType<Date> clone() {
return this;
}
@Override
public int compare(Date t1, Date t2) {
if (t1 == null && t2 == null) {
return 0;
} else if (t1 == null) {
return +1;
} else if (t2 == null) {
return -1;
}
return t1.compareTo(t2);
}
@Override
public int compare(final String s1, final String s2) throws ParseException {
try {
Date d1 = parse(s1);
Date d2 = parse(s2);
if (d1 == null && d2 == null) {
return 0;
} else if (d1 == null) {
return +1;
} else if (d2 == null) {
return -1;
}
return d1.compareTo(d2);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid value", e);
}
}
@Override
public Date divide(Date dividend, Date divisor) {
long d1 = dividend.getTime();
long d2 = divisor.getTime();
return new Date(d1 / d2);
}
@Override
public String divide(String dividend, String divisor) {
long d1 = parse(dividend).getTime();
long d2 = parse(divisor).getTime();
return format(new Date(d1 / d2));
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
final ARXDate other = (ARXDate) obj;
if (string == null) { if (other.string != null) { return false; }
} else if (!string.equals(other.string)) { return false; }
if (getLocale() == null) { if (other.getLocale() != null) { return false; }
} else if (!getLocale().equals(other.getLocale())) { return false; }
return true;
}
@Override
public String format(Date s){
if (s == null) {
return NULL_VALUE;
}
return format.format(s);
}
@Override
public Date fromDouble(Double d) {
if (d == null) {
return null;
} else {
return new Date(Math.round(d));
}
}
@Override
public DataTypeDescription<Date> getDescription(){
return description;
}
@Override
public String getFormat() {
return string;
}
/**
* Returns the locale of the format.
*
* @return
*/
public Locale getLocale() {
if (this.locale == null) {
return Locale.getDefault();
} else {
return locale;
}
}
@Override
public Date getMaximum() {
return new Date(Long.MAX_VALUE);
}
@Override
public Date getMinimum() {
return new Date(Long.MIN_VALUE);
}
@Override
public int hashCode() {
if (string == null) {
return getLocale().hashCode();
}
else {
final int prime = 31;
int result = 1;
result = prime * result + ((string == null) ? 0 : string.hashCode());
result = prime * result + ((getLocale() == null) ? 0 : getLocale().hashCode());
return result;
}
}
@Override
public boolean isValid(String s) {
try {
parse(s);
return true;
} catch (Exception e){
return false;
}
}
@Override
public Date multiply(Date multiplicand, Date multiplicator) {
long d1 = multiplicand.getTime();
long d2 = multiplicator.getTime();
return new Date(d1 * d2);
}
@Override
public Date multiply(Date multiplicand, double multiplicator) {
long d1 = multiplicand.getTime();
return new Date((long)((double)d1 * multiplicator));
}
@Override
public Date multiply(Date multiplicand, int multiplicator) {
long d1 = multiplicand.getTime();
return new Date(d1 * multiplicator);
}
@Override
public String multiply(String multiplicand, String multiplicator) {
long d1 = parse(multiplicand).getTime();
long d2 = parse(multiplicator).getTime();
return format(new Date(d1 * d2));
}
@Override
public Date parse(String s) {
if(s.length() == NULL_VALUE.length() && s.toUpperCase().equals(NULL_VALUE)) {
return null;
}
try {
return format.parse(s);
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage() + ": " + s, e);
}
}
@Override
public double ratio(Date dividend, Date divisor) {
long d1 = dividend.getTime();
long d2 = divisor.getTime();
return (double)d1 / (double)d2;
}
@Override
public Date subtract(Date minuend, Date subtrahend) {
long d1 = minuend.getTime();
long d2 = subtrahend.getTime();
return new Date(d1 - d2);
}
@Override
public Double toDouble(Date date) {
return (date == null) ? null : new Long(date.getTime()).doubleValue();
}
@Override
public String toString() {
return "Date(" + string + ")";
}
}
/**
* Base class for numeric types.
*
* @author Fabian Prasser
*/
public static class ARXDecimal extends DataType<Double> implements DataTypeWithFormat, DataTypeWithRatioScale<Double> {
/** SVUID */
private static final long serialVersionUID = 7293446977526103610L;
/** The description of the data type. */
private static final DataTypeDescription<Double> description = new DataTypeDescription<Double>(Double.class, "Decimal", DataScale.RATIO, true, listDecimalFormats()){
private static final long serialVersionUID = -3549629178680030868L;
@Override public DataType<Double> newInstance() { return DECIMAL; }
@Override public DataType<Double> newInstance(String format) {return createDecimal(format);}
@Override public DataType<Double> newInstance(String format, Locale locale) {return createDecimal(format, locale);}
};
/** Format. */
private final DecimalFormat format;
/** Format string. */
private final String string;
/** Locale. */
private final Locale locale;
/**
* Default constructor.
*/
private ARXDecimal(){
this("Default");
}
/**
* Create a numeric with a format string and default locale. Format strings must be valid formats
* for <code>DecimalFormat</code>.
* @param format
* @see <a href="http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html">DecimalFormat</a>
*/
private ARXDecimal(String format){
if (format == null || format.equals("Default")){
this.format = null;
this.string = null;
this.locale = null;
} else {
this.format = new DecimalFormat(format);
this.string = format;
this.locale = null;
}
}
/**
* Create a numeric with a format string and given locale. Format strings must be valid formats
* for <code>DecimalFormat</code>.
* @param format
* @param locale
* @see <a href="http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html">DecimalFormat</a>
*/
private ARXDecimal(String format, Locale locale) {
if (format == null || format.equals("Default")){
this.format = null;
this.string = null;
this.locale = locale;
} else {
this.format = new DecimalFormat(format, new DecimalFormatSymbols(locale));
this.string = format;
this.locale = locale;
}
}
@Override
public Double add(Double augend, Double addend) {
return parse(format(augend + addend));
}
@Override
public DataType<Double> clone() {
return this;
}
@Override
public int compare(Double t1, Double t2) {
if (t1 == null && t2 == null) {
return 0;
} else if (t1 == null) {
return +1;
} else if (t2 == null) {
return -1;
}
double d1 = parse(format(t1));
double d2 = parse(format(t2));
d1 = d1 == -0.0d ? 0d : d1;
d2 = d2 == -0.0d ? 0d : d2;
return Double.valueOf(d1).compareTo(Double.valueOf(d2));
}
@Override
public int compare(final String s1, final String s2) throws NumberFormatException {
try {
Double d1 = parse(s1);
Double d2 = parse(s2);
if (d1 == null && d2 == null) {
return 0;
} else if (d1 == null) {
return +1;
} else if (d2 == null) {
return -1;
}
d1 = d1.doubleValue() == -0.0d ? 0d : d1;
d2 = d2.doubleValue() == -0.0d ? 0d : d2;
return d1.compareTo(d2);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid value: '"+s1+"' or '"+s2+"'", e);
}
}
@Override
public Double divide(Double dividend, Double divisor) {
return parse(format(dividend / divisor));
}
@Override
public String divide(String dividend, String divisor) {
Double d1 = parse(dividend);
Double d2 = parse(divisor);
return format(d1 / d2);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
final ARXDecimal other = (ARXDecimal) obj;
if (string == null) { if (other.string != null) { return false; }
} else if (!string.equals(other.string)) { return false; }
if (getLocale() == null) { if (other.getLocale() != null) { return false; }
} else if (!getLocale().equals(other.getLocale())) { return false; }
return true;
}
@Override
public String format(Double s){
if (s == null) {
return NULL_VALUE;
}
if (format==null){
return String.valueOf(s);
} else {
return format.format(s);
}
}
@Override
public Double fromDouble(Double d) {
if (d == null) {
return null;
} else {
return d;
}
}
@Override
public DataTypeDescription<Double> getDescription(){
return description;
}
@Override
public String getFormat() {
return string;
}
/**
* Returns the locale of the format.
*
* @return
*/
public Locale getLocale() {
if (this.locale == null) {
return Locale.getDefault();
} else {
return locale;
}
}
@Override
public Double getMaximum() {
return Double.MAX_VALUE;
}
@Override
public Double getMinimum() {
return -Double.MAX_VALUE;
}
@Override
public int hashCode() {
if (string==null) {
return getLocale().hashCode();
}
else {
final int prime = 31;
int result = 1;
result = prime * result + ((string == null) ? 0 : string.hashCode());
result = prime * result + ((getLocale() == null) ? 0 : getLocale().hashCode());
return result;
}
}
@Override
public boolean isValid(String s) {
try {
parse(s);
return true;
} catch (Exception e){
return false;
}
}
@Override
public Double multiply(Double multiplicand, double multiplicator) {
return parse(format(multiplicand * multiplicator));
}
@Override
public Double multiply(Double multiplicand, Double multiplicator) {
return parse(format(multiplicand * multiplicator));
}
@Override
public Double multiply(Double multiplicand, int multiplicator) {
return parse(format(multiplicand* multiplicator));
}
@Override
public String multiply(String multiplicand, String multiplicator) {
Double d1 = parse(multiplicand);
Double d2 = parse(multiplicator);
return format(d1 * d2);
}
@Override
public Double parse(String s) {
if(s.length() == NULL_VALUE.length() && s.toUpperCase().equals(NULL_VALUE)) {
return null;
}
try {
if (format == null) {
return Double.valueOf(s);
} else {
return format.parse(s).doubleValue();
}
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage() + ": " + s, e);
}
}
@Override
public double ratio(Double dividend, Double divisor) {
return parse(format(dividend / divisor));
}
@Override
public Double subtract(Double minuend, Double subtrahend) {
return parse(format(minuend - subtrahend));
}
@Override
public Double toDouble(Double val) {
return val;
}
@Override
public String toString() {
return "Decimal";
}
}
/**
* Base class for numeric types.
*
* @author Fabian Prasser
*/
public static class ARXInteger extends DataType<Long> implements DataTypeWithFormat, DataTypeWithRatioScale<Long> {
/** SVUID */
private static final long serialVersionUID = -631163546929231044L;
/** The description of the data type. */
private static final DataTypeDescription<Long> description = new DataTypeDescription<Long>(Long.class, "Integer", DataScale.RATIO, false, new ArrayList<String>()){
private static final long serialVersionUID = -4498725217659811835L;
@Override public DataType<Long> newInstance() { return INTEGER; }
@Override public DataType<Long> newInstance(String format) {return createInteger(format);}
@Override public DataType<Long> newInstance(String format, Locale locale) {return createInteger(format, locale);}
};
/** Format. */
private final DecimalFormat format;
/** Format string. */
private final String string;
/** Locale. */
private final Locale locale;
/**
* Default constructor.
*/
private ARXInteger(){
this("Default");
}
/**
* Create a numeric with a format string and default locale. Format strings must be valid formats
* for <code>DecimalFormat</code>.
* @param format
* @see <a href="http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html">DecimalFormat</a>
*/
private ARXInteger(String format){
if (format == null || format.equals("Default")){
this.format = null;
this.string = null;
this.locale = null;
} else {
this.format = new DecimalFormat(format);
this.string = format;
this.locale = null;
}
}
/**
* Create a numeric with a format string. Format strings must be valid formats
* for <code>DecimalFormat</code>.
*
* @param format
* @param locale
* @see <a href="http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html">DecimalFormat</a>
*/
private ARXInteger(String format, Locale locale){
if (format == null || format.equals("Default")){
this.format = null;
this.string = null;
this.locale = locale;
} else {
this.format = new DecimalFormat(format, new DecimalFormatSymbols(locale));
this.string = format;
this.locale = locale;
}
}
@Override
public Long add(Long augend, Long addend) {
return augend + addend;
}
@Override
public DataType<Long> clone() {
return this;
}
@Override
public int compare(Long t1, Long t2) {
if (t1 == null && t2 == null) {
return 0;
} else if (t1 == null) {
return +1;
} else if (t2 == null) {
return -1;
}
return t1.compareTo(t2);
}
@Override
public int compare(final String s1, final String s2) throws NumberFormatException {
try {
Long d1 = parse(s1);
Long d2 = parse(s2);
if (d1 == null && d2 == null) {
return 0;
} else if (d1 == null) {
return +1;
} else if (d2 == null) {
return -1;
}
return d1.compareTo(d2);
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage() + ": " + s1 +" or " + s2, e);
}
}
@Override
public Long divide(Long dividend, Long divisor) {
return (long)Math.round((double)dividend / (double)divisor);
}
@Override
public String divide(String dividend, String divisor) {
Long d1 = parse(dividend);
Long d2 = parse(divisor);
return format(d1 / d2);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
final ARXInteger other = (ARXInteger) obj;
if (string == null) { if (other.string != null) { return false; }
} else if (!string.equals(other.string)) { return false; }
if (getLocale() == null) { if (other.getLocale() != null) { return false; }
} else if (!getLocale().equals(other.getLocale())) { return false; }
return true;
}
@Override
public String format(Long s){
if (s == null) {
return NULL_VALUE;
}
if (format==null){
return String.valueOf(s);
} else {
return format.format(s);
}
}
@Override
public Long fromDouble(Double d) {
if (d == null) {
return null;
} else {
return Math.round(d);
}
}
@Override
public DataTypeDescription<Long> getDescription(){
return description;
}
@Override
public String getFormat() {
return string;
}
/**
* Returns the locale of the format.
*
* @return
*/
public Locale getLocale() {
if (this.locale == null) {
return Locale.getDefault();
} else {
return locale;
}
}
@Override
public Long getMaximum() {
return Long.MAX_VALUE;
}
@Override
public Long getMinimum() {
return Long.MIN_VALUE;
}
@Override
public int hashCode() {
if (string == null) {
return getLocale().hashCode();
}
else {
final int prime = 31;
int result = 1;
result = prime * result + ((string == null) ? 0 : string.hashCode());
result = prime * result + ((getLocale() == null) ? 0 : getLocale().hashCode());
return result;
}
}
@Override
public boolean isValid(String s) {
try {
parse(s);
return true;
} catch (Exception e){
return false;
}
}
@Override
public Long multiply(Long multiplicand, double multiplicator) {
return (long)((double)multiplicand * multiplicator);
}
@Override
public Long multiply(Long multiplicand, int multiplicator) {
return multiplicand * multiplicator;
}
@Override
public Long multiply(Long multiplicand, Long multiplicator) {
return (long)Math.round((double)multiplicand * (double)multiplicator);
}
@Override
public String multiply(String multiplicand, String multiplicator) {
Long d1 = parse(multiplicand);
Long d2 = parse(multiplicator);
return format(d1 * d2);
}
@Override
public Long parse(String s) {
if(s.length() == NULL_VALUE.length() && s.toUpperCase().equals(NULL_VALUE)) {
return null;
}
try {
if (format == null) {
return Long.valueOf(s);
} else {
return format.parse(s).longValue();
}
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage() + ": " + s, e);
}
}
@Override
public double ratio(Long dividend, Long divisor) {
return (double)dividend / (double)divisor;
}
@Override
public Long subtract(Long minuend, Long subtrahend) {
return minuend - subtrahend;
}
@Override
public Double toDouble(Long val) {
return (val == null) ? null : val.doubleValue();
}
@Override
public String toString() {
return "Integer";
}
}
/**
* Base class for ordered string types.
*
* @author Fabian Prasser
*/
public static class ARXOrderedString extends DataType<String> implements DataTypeWithFormat {
/** SVUID */
private static final long serialVersionUID = -830897705078418835L;
/** The defined order */
private final Map<String, Integer> order;
/** The description of the data type. */
private static final DataTypeDescription<String> description = new DataTypeDescription<String>(String.class, "Ordinal", DataScale.ORDINAL, true, new ArrayList<String>()){
private static final long serialVersionUID = -6300869938311742699L;
@Override public DataType<String> newInstance() { return ORDERED_STRING; }
@Override public DataType<String> newInstance(String format) {return createOrderedString(format);}
@Override public DataType<String> newInstance(String format, Locale locale) {return createOrderedString(format);}
};
/**
* Creates a new instance.
*/
private ARXOrderedString(){
this("Default");
}
/**
* Creates a new instance.
*
* @param format Ordered list of strings
*/
private ARXOrderedString(List<String> format){
if (format.size()==0) {
this.order = null;
} else {
this.order = new HashMap<String, Integer>();
for (int i=0; i< format.size(); i++){
if (this.order.put(format.get(i), i) != null) {
throw new IllegalArgumentException("Duplicate value '"+format.get(i)+"'");
}
}
}
}
/**
* Creates a new instance.
*
* @param format Ordered list of string separated by line feeds
*/
private ARXOrderedString(String format){
if (format==null || format.equals("Default") || format.equals("")) {
this.order = null;
} else {
try {
this.order = new HashMap<String, Integer>();
BufferedReader reader = new BufferedReader(new StringReader(format));
int index = 0;
String line = reader.readLine();
while (line != null) {
if (this.order.put(line, index) != null) {
throw new IllegalArgumentException("Duplicate value '"+line+"'");
}
line = reader.readLine();
index++;
}
reader.close();
} catch (IOException e) {
throw new IllegalArgumentException("Error reading input data");
}
}
}
/**
* Creates a new instance.
*
* @param format Ordered list of strings
*/
private ARXOrderedString(String[] format){
if (format.length == 0) {
this.order = null;
} else {
this.order = new HashMap<String, Integer>();
for (int i=0; i< format.length; i++){
if (this.order.put(format[i], i) != null) {
throw new IllegalArgumentException("Duplicate value '"+format[i]+"'");
}
}
}
}
@Override
public DataType<String> clone() {
return this;
}
@Override
public int compare(String s1, String s2) {
s1 = parse(s1);
s2 = parse(s2);
if (s1 == null && s2 == null) {
return 0;
} else if (s1 == null) {
return +1;
} else if (s2 == null) {
return -1;
}
if (order != null){
try {
return order.get(s1).compareTo(order.get(s2));
} catch (Exception e) {
throw new IllegalArgumentException("Invalid value", e);
}
} else {
return s1.compareTo(s2);
}
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
if (this.order == null) {
if (((ARXOrderedString)obj).order != null) {
return false;
}
} else {
if (!((ARXOrderedString)obj).order.equals(this.order)) {
return false;
}
}
return true;
}
@Override
public String format(String s){
if (s == null) {
return NULL_VALUE;
}
if (order != null && !order.containsKey(s)) {
throw new IllegalArgumentException("Unknown string '"+s+"'");
}
return s;
}
@Override
public DataTypeDescription<String> getDescription(){
return description;
}
/**
* Returns all elements backing this datatype.
*
* @return
*/
public List<String> getElements() {
List<String> result = new ArrayList<String>();
if (order == null) {
return result;
}
result.addAll(order.keySet());
Collections.sort(result, new Comparator<String>(){
@Override public int compare(String arg0, String arg1) {
return order.get(arg0).compareTo(order.get(arg1));
}
});
return result;
}
@Override
public String getFormat() {
if (order == null) return "";
List<String> list = new ArrayList<String>();
list.addAll(order.keySet());
Collections.sort(list, new Comparator<String>(){
@Override
public int compare(String arg0, String arg1) {
return order.get(arg0).compareTo(order.get(arg1));
}
});
StringBuilder b = new StringBuilder();
for (int i=0; i<list.size(); i++) {
b.append(list.get(i));
if (i<list.size()-1) {
b.append("\n");
}
}
return b.toString();
}
/**
* Returns the locale of the format.
*
* @return
*/
public Locale getLocale() {
return Locale.getDefault();
}
@Override
public int hashCode() {
return ARXOrderedString.class.hashCode();
}
@Override
public boolean isValid(String s) {
if (s.length() == NULL_VALUE.length() && s.toUpperCase().equals(NULL_VALUE)) {
return true;
} else if (order != null && !order.containsKey(s)) {
return false;
} else {
return true;
}
}
@Override
public String parse(String s) {
if(s.length() == NULL_VALUE.length() && s.toUpperCase().equals(NULL_VALUE)) {
return null;
}
if (order != null && !order.containsKey(s)) {
throw new IllegalArgumentException("Unknown string '"+s+"'");
}
return s;
}
@Override
public String toString() {
return "Ordinal";
}
}
/**
* Base class for string types.
*
* @author Fabian Prasser
*/
public static class ARXString extends DataType<String> {
/** SVUID */
private static final long serialVersionUID = 903334212175979691L;
/** The description of the data type. */
private static final DataTypeDescription<String> description = new DataTypeDescription<String>(String.class, "String", DataScale.NOMINAL, false, new ArrayList<String>()){
private static final long serialVersionUID = -6679110898204862834L;
@Override public DataType<String> newInstance() { return STRING; }
@Override public DataType<String> newInstance(String format) {return STRING;}
@Override public DataType<String> newInstance(String format, Locale locale) {return STRING;}
};
@Override
public DataType<String> clone() {
return this;
}
@Override
public int compare(final String s1, final String s2) {
if (s1 == null || s2 == null) {
throw new IllegalArgumentException("Null is not a string");
}
return s1.compareTo(s2);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) { return true; }
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
return true;
}
@Override
public String format(String s){
if (s == null) {
throw new IllegalArgumentException("Null is not a string");
}
return s;
}
@Override
public DataTypeDescription<String> getDescription(){
return description;
}
@Override
public int hashCode() {
return ARXString.class.hashCode();
}
@Override
public boolean isValid(String s) {
return s != null;
}
@Override
public String parse(String s) {
if (s == null) {
throw new IllegalArgumentException("Null is not a string");
}
return s;
}
@Override
public String toString() {
return "String";
}
}
/**
* An entry in the list of available data types.
*
* @author Fabian Prasser
* @param <T>
*/
public static abstract class DataTypeDescription<T> implements Serializable {
/** SVUID */
private static final long serialVersionUID = 6369986224526795419L;
/** The wrapped java class. */
private Class<?> clazz;
/** If yes, a list of available formats. */
private List<String> exampleFormats;
/** Can the type be parameterized with a format string. */
private boolean hasFormat;
/** A human readable label. */
private String label;
/** The associated scale of measure*/
private DataScale scale;
/**
* Internal constructor.
*
* @param clazz
* @param label
* @param scale
* @param hasFormat
* @param exampleFormats
*/
private DataTypeDescription(Class<T> clazz,
String label,
DataScale scale,
boolean hasFormat,
List<String> exampleFormats) {
this.clazz = clazz;
this.label = label;
this.scale = scale;
this.hasFormat = hasFormat;
this.exampleFormats = exampleFormats;
}
/**
* Returns a list of example formats.
*
* @return
*/
public List<String> getExampleFormats() {
return exampleFormats;
}
/**
* Returns a human readable label.
*
* @return
*/
public String getLabel() {
return label;
}
public DataScale getScale() {
return scale;
}
/**
* Returns the wrapped java class.
*
* @return
*/
public Class<?> getWrappedClass() {
return clazz;
}
/**
* Returns whether the type be parameterized with a format string. Note that every data type
* can be instantiated without a format string, using a default format.
* @return
*/
public boolean hasFormat() {
return hasFormat;
}
/**
* Creates a new instance with default format string and default locale.
*
* @return
*/
public abstract DataType<T> newInstance();
/**
* Creates a new instance with the given format string and default locale.
*
* @param format
* @return
*/
public abstract DataType<T> newInstance(String format);
/**
* Creates a new instance with the given format string and the given locale.
*
* @param format
* @param locale
* @return
*/
public abstract DataType<T> newInstance(String format, Locale locale);
}
/**
* An interface for data types with format.
*
* @author Fabian Prasser
*/
public static interface DataTypeWithFormat {
/**
* @return
*/
public abstract String getFormat();
/**
* @return
*/
public abstract Locale getLocale();
}
/**
* An interface for data types with a ratio scale.
*
* @author Fabian Prasser
* @param <T>
*/
public static interface DataTypeWithRatioScale<T> {
/**
*
*
* @param augend
* @param addend
* @return
*/
public abstract T add(T augend, T addend);
/**
*
*
* @param s1
* @param s2
* @return
* @throws NumberFormatException
* @throws ParseException
*/
public abstract int compare(String s1, String s2) throws NumberFormatException,
ParseException;
/**
*
*
* @param t1
* @param t2
* @return
*/
public abstract int compare(T t1, T t2);
/**
*
*
* @param dividend
* @param divisor
* @return
*/
public abstract String divide(String dividend, String divisor);
/**
*
*
* @param dividend
* @param divisor
* @return
*/
public abstract T divide(T dividend, T divisor);
/**
*
*
* @param t
* @return
*/
public abstract String format(T t);
/**
* Converts a double into a value.
*
* @param s
* @return
*/
public abstract T fromDouble(Double d);
/**
*
*
* @return
*/
public abstract DataTypeDescription<T> getDescription();
/**
*
*
* @return
*/
public T getMaximum();
/**
*
*
* @return
*/
public T getMinimum();
/**
*
*
* @param s
* @return
*/
public abstract boolean isValid(String s);
/**
*
*
* @param multiplicand
* @param multiplicator
* @return
*/
public abstract String multiply(String multiplicand,
String multiplicator);
/**
*
*
* @param multiplicand
* @param multiplicator
* @return
*/
public abstract T multiply(T multiplicand, double multiplicator);
/**
*
*
* @param multiplicand
* @param multiplicator
* @return
*/
public abstract T multiply(T multiplicand, int multiplicator);
/**
*
*
* @param multiplicand
* @param multiplicator
* @return
*/
public abstract T multiply(T multiplicand, T multiplicator);
/**
*
*
* @param s
* @return
*/
public abstract T parse(String s);
/**
*
*
* @param dividend
* @param divisor
* @return
*/
public abstract double ratio(T dividend, T divisor);
/**
*
*
* @param minuend
* @param subtrahend
* @return
*/
public abstract T subtract(T minuend, T subtrahend);
/**
* Converts a double into a value.
*
* @param s
* @return
*/
public abstract Double toDouble(T t);
}
/** The string representing the NULL value */
public static final String NULL_VALUE = "NULL";
/** The string representing the ANY value */
public static final String ANY_VALUE = "*";
/** SVUID */
private static final long serialVersionUID = -4380267779210935078L;
/** A date data type with default format dd.mm.yyyy */
public static final DataType<Date> DATE = new ARXDate();
/** A generic decimal data type. */
public static final DataType<Double> DECIMAL = new ARXDecimal();
/** A generic integer data type. */
public static final DataType<Long> INTEGER = new ARXInteger();
/** A string data type. */
public static final DataType<String> STRING = new ARXString();
/** A ordered string data type. */
public static final DataType<String> ORDERED_STRING = new ARXOrderedString();
/**
* A date data type with given format.
*
* @param format
* @return
* @see SimpleDateFormat
*/
public static final DataType<Date> createDate(final String format) {
return new ARXDate(format);
}
/**
* A date data type with given format.
*
* @param format
* @param locale
* @return
* @see SimpleDateFormat
*/
public static final DataType<Date> createDate(final String format, final Locale locale) {
return new ARXDate(format, locale);
}
/**
* A decimal data type with given format.
*
* @param format
* @return
* @see DecimalFormat
*/
public static final DataType<Double> createDecimal(final String format) {
return new ARXDecimal(format);
}
/**
* Creates a decimal data type with a format string from the given locale.
*
* @param format
* @param locale
* @return
*/
public static DataType<Double> createDecimal(String format, Locale locale) {
return new ARXDecimal(format, locale);
}
/**
* An integer data type with given format.
*
* @param format
* @return
* @see DecimalFormat
*/
public static final DataType<Long> createInteger(final String format) {
return new ARXInteger(format);
}
/**
* An integer data type with given format using the given locale.
*
* @param format
* @param locale
* @return
* @see DecimalFormat
*/
public static final DataType<Long> createInteger(final String format, Locale locale) {
return new ARXInteger(format, locale);
}
/**
* A ordered string type with given format.
*
* @param format List of ordered strings
* @return
*/
public static final DataType<String> createOrderedString(final List<String> format) {
return new ARXOrderedString(format);
}
/**
* A ordered string type with given format.
*
* @param format List of ordered strings separated by line feeds
* @return
*/
public static final DataType<String> createOrderedString(final String format) {
return new ARXOrderedString(format);
}
/**
* A ordered string type with given format.
*
* @param format List of ordered strings
* @return
*/
public static final DataType<String> createOrderedString(final String[] format) {
return new ARXOrderedString(format);
}
/**
* Returns whether the value represents any value
* @param value
* @return
*/
public static final boolean isAny(String value) {
return value != null && value.equals(ANY_VALUE);
}
/**
* Returns whether the value represents null
* @param value
* @return
*/
public static final boolean isNull(String value) {
return value == null || (value.length() == NULL_VALUE.length() && value.toUpperCase().equals(NULL_VALUE));
}
/**
* Lists all available data types.
*
* @return
*/
public static final List<DataTypeDescription<?>> list(){
List<DataTypeDescription<?>> list = new ArrayList<DataTypeDescription<?>>();
list.add(STRING.getDescription());
list.add(ORDERED_STRING.getDescription());
list.add(DATE.getDescription());
list.add(DECIMAL.getDescription());
list.add(INTEGER.getDescription());
return list;
}
/**
*
* Returns a datatype for the given class.
*
* @param <U>
* @param clazz
* @return
*/
@SuppressWarnings("unchecked")
public static final <U> DataTypeDescription<U> list(Class<U> clazz){
for (DataTypeDescription<?> entry : list()) {
if (entry.getWrappedClass() == clazz) {
return (DataTypeDescription<U>)entry;
}
}
return null;
}
/**
* Provides a list of example formats for the <code>Date</code> data type.
*
* @return
*/
private static List<String> listDateFormats(){
List<String> result = new ArrayList<String>();
result.add("yyyy-MM-dd'T'HH:mm:ss'Z'");
result.add("yyyy-MM-ddZZ");
result.add("yyyy-MM-dd'T'HH:mm:ssz");
result.add("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
result.add("EEE MMM d hh:mm:ss z yyyy");
result.add("EEE MMM dd HH:mm:ss yyyy");
result.add("EEEE, dd-MMM-yy HH:mm:ss zzz");
result.add("EEE, dd MMM yyyy HH:mm:ss zzz");
result.add("EEE, dd MMM yy HH:mm:ss z");
result.add("EEE, dd MMM yy HH:mm z");
result.add("EEE, dd MMM yyyy HH:mm:ss z");
result.add("yyyy-MM-dd'T'HH:mm:ss");
result.add("EEE, dd MMM yyyy HH:mm:ss Z");
result.add("dd MMM yy HH:mm:ss z");
result.add("dd MMM yy HH:mm z");
result.add("'T'HH:mm:ss");
result.add("'T'HH:mm:ssZZ");
result.add("HH:mm:ss");
result.add("HH:mm:ssZZ");
result.add("yyyy-MM-dd");
result.add("yyyy-MM-dd hh:mm:ss");
result.add("yyyy-MM-dd HH:mm:ss");
result.add("yyyy-MM-dd'T'HH:mm:ssz");
result.add("yyyy-MM-dd'T'HH:mm:ss");
result.add("yyyy-MM-dd'T'HH:mm:ssZZ");
result.add("dd.MM.yyyy");
result.add("dd.MM.yyyy hh:mm:ss");
result.add("dd.MM.yyyy HH:mm:ss");
result.add("dd.MM.yyyy'T'HH:mm:ssz");
result.add("dd.MM.yyyy'T'HH:mm:ss");
result.add("dd.MM.yyyy'T'HH:mm:ssZZ");
result.add("dd/MM/yyyy");
result.add("dd/MM/yy");
result.add("MM/dd/yyyy");
result.add("MM/dd/yy");
result.add("MM/dd/yyyy hh:mm:ss");
result.add("MM/dd/yy hh:mm:ss");
return result;
}
/**
* Provides a list of example formats for the <code>Decimal</code> data type.
*
* @return
*/
private static List<String> listDecimalFormats(){
List<String> result = new ArrayList<String>();
result.add("#,##0");
result.add("#,##0.###");
result.add("#,##0%");
result.add("¤#,##0.00;(¤#,##0.00)");
return result;
}
@Override
public abstract DataType<T> clone();
/**
* Compares two values. The result is 0 if both values are equal,
* less than 0 if the first value is less than the second argument,
* and greater than 0 if the first value is greater than the second argument.
* @param s1
* @param s2
* @return
* @throws NumberFormatException
* @throws ParseException
*/
public abstract int compare(String s1, String s2) throws NumberFormatException, ParseException;
/**
* Compare.
*
* @param t1
* @param t2
* @return
*/
public abstract int compare(T t1, T t2);
/**
* Returns a new function builder.
*
* @return
*/
public AggregateFunctionBuilder<T> createAggregate(){
return AggregateFunction.forType(this);
}
@Override
public abstract boolean equals(Object other);
/**
* Converts a value into a string.
*
* @param t
* @return
*/
public abstract String format(T t);
/**
* Returns a description of the data type.
*
* @return
*/
public abstract DataTypeDescription<T> getDescription();
@Override
public abstract int hashCode();
/**
* Checks whether the given string conforms to the data type's format.
*
* @param s
* @return
*/
public abstract boolean isValid(String s);
/**
* Converts a string into a value.
*
* @param s
* @return
*/
public abstract T parse(String s);
}