private GregorianDateIDPartOutputFormatter formatter;
private IDPartValidator validator = IDPart.DEFAULT_VALIDATOR;
+ // Because the output from newID() is dependent on a
+ // format pattern having been set in the outputFormatter,
+ // constructors for this class all require that pattern.
+
public GregorianDateIDPart(String formatPattern) {
- setFormatter(new GregorianDateIDPartOutputFormatter(formatPattern));
+ setOutputFormatter(new GregorianDateIDPartOutputFormatter(formatPattern));
+ }
+
+ public GregorianDateIDPart(String formatPattern, IDPartValidator validator) {
+ setOutputFormatter(new GregorianDateIDPartOutputFormatter(formatPattern));
+ setValidator(validator);
}
public GregorianDateIDPart(String formatPattern, String languageCode) {
- setFormatter(
+ setOutputFormatter(
new GregorianDateIDPartOutputFormatter(formatPattern, languageCode));
}
- public GregorianDateIDPart(String formatPattern, IDPartValidator validator) {
- setFormatter(new GregorianDateIDPartOutputFormatter(formatPattern));
+ public GregorianDateIDPart(String formatPattern, String languageCode,
+ IDPartValidator validator) {
+ setOutputFormatter(
+ new GregorianDateIDPartOutputFormatter(formatPattern, languageCode));
setValidator(validator);
}
return this.formatter;
}
+ private void setOutputFormatter(GregorianDateIDPartOutputFormatter formatter) {
+ this.formatter = formatter;
+ }
+
@Override
public IDPartValidator getValidator() {
return this.validator;
}
+ private void setValidator(IDPartValidator validator) {
+ throw new UnsupportedOperationException("Not yet implemented");
+ }
+
@Override
public String newID() {
return getOutputFormatter().format(Long.toString(cal.getTime().getTime()));
}
- private void setFormatter(GregorianDateIDPartOutputFormatter formatter) {
- this.formatter = formatter;
- }
-
- private void setValidator(IDPartValidator validator) {
- throw new UnsupportedOperationException("Not yet implemented");
- }
-
}
package org.collectionspace.services.id.part;
-import java.util.Date;
-import java.util.Locale;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
-
import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
LoggerFactory.getLogger(GregorianDateIDPartOutputFormatter.class);
private int maxOutputLength = DEFAULT_MAX_OUTPUT_LENGTH;
- private Locale locale = null;
- private String language;
private String formatPattern;
+ private final String DEFAULT_ISO_8601_FORMAT_PATTERN =
+ "yyyy-MM-dd"; // Note: 'floating date' without time zone.
+ private final String DEFAULT_GREGORIAN_DATE_FORMAT_PATTERN =
+ DEFAULT_ISO_8601_FORMAT_PATTERN;
+ private Locale locale = null;
+
+ public GregorianDateIDPartOutputFormatter() {
+ setFormatPattern(DEFAULT_GREGORIAN_DATE_FORMAT_PATTERN);
+ setLocale(Locale.getDefault());
+ }
public GregorianDateIDPartOutputFormatter(String formatPattern) {
setFormatPattern(formatPattern);
return this.maxOutputLength;
}
+ @Override
public void setMaxOutputLength (int length) {
this.maxOutputLength = length;
}
return this.formatPattern;
}
+ @Override
public void setFormatPattern(String pattern) {
if (pattern == null || pattern.trim().isEmpty()) {
logger.error("Format pattern cannot be null or empty.");
@Override
public String format(String id) {
+ String pattern = getFormatPattern();
+ // If the formatting pattern is empty, output using
+ // a default formatting pattern.
+ if (pattern == null || pattern.trim().isEmpty()) {
+ pattern = DEFAULT_GREGORIAN_DATE_FORMAT_PATTERN;
+ }
+
+ // Convert milliseconds in Epoch to Date.
Long millisecondsInEpoch = 0L;
try {
millisecondsInEpoch = (Long.parseLong(id));
logger.error("Could not parse date milliseconds as a number.", e);
return "";
}
-
- String formattedID = "";
- if (millisecondsInEpoch > 0) {
- Date d = new Date(millisecondsInEpoch);
- formattedID = formatDate(d);
-
- // @TODO Check for exceeding maximum length before
- // returning formatted date value.
+
+ if (millisecondsInEpoch <= 0) {
+ return "";
+
+ }
+
+ // Format the date using the specified format pattern.
+ Date d = new Date(millisecondsInEpoch);
+ String formattedID = formatDate(d);
+
+ if (! isValidLength(formattedID)) {
+ logger.error(
+ "Formatted ID '" + formattedID +
+ "' exceeds maximum length of " +
+ getMaxOutputLength() + " characters.");
+ return "";
}
return formattedID;
return dateformatter.format(date);
}
+ private boolean isValidLength(String id) {
+ if (id.length() <= getMaxOutputLength()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
// @TODO Consider generalizing locale-specific operations
// in a utility class outside of ID package.
if (languageCode.length() != 2) {
logger.error(
"Locale language code '" + languageCode +
- "' must be a two-letter ISO-639 language code.");
+ "' must be a two-letter ISO 639-1 language code.");
return;
}
- // Although language codes are documented as required to be
- // in lowercase, and they are output in that way in
+ // Although it is documented in Sun's Javadocs that
+ // the language code parameter when initializing a
+ // Locale is required to be in lowercase, and as well,
+ // language codes are output only in lowercase in
// DateFormat.getAvailableLocales(), they appear to be
// matched - within Locales - in a case-insensitive manner.
- /*
- if (! languageCode.equals(languageCode.toLowerCase())) {
- logger.error("Locale language code must be in lower case.");
- return;
- }
- */
+ // If that ever changes, uncomment the following block.
+ //
+ // if (! languageCode.equals(languageCode.toLowerCase())) {
+ // logger.error("Locale language code must be in lower case.");
+ // return;
+ // }
Locale l = new Locale(languageCode, "");
- if (isValidLocaleForDateFormatter(l)) {
+ if (isValidLocaleForFormatter(l)) {
setLocale(l);
} else {
logger.error("Locale language code '" + languageCode +
}
}
- private boolean isValidLocaleForDateFormatter(Locale l) {
+ private boolean isValidLocaleForFormatter(Locale l) {
Locale[] locales = DateFormat.getAvailableLocales();
return (Arrays.asList(locales).contains(l)) ? true : false;
}
- private boolean isValidLength(String id) {
- if (id.length() > getMaxOutputLength()) {
- return false;
- } else {
- return true;
- }
- }
-
}
public int getMaxOutputLength();
+ public void setMaxOutputLength(int length);
+
public String getFormatPattern();
+ public void setFormatPattern(String pattern);
+
+ // @TODO Consider throwing IllegalStateException from this method.
public String format(String id);
}
package org.collectionspace.services.id.part;
-import java.util.IllegalFormatException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.IllegalFormatException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
// Uses Java's interpreter for printf-style format strings.
// http://java.sun.com/javase/1.6/docs/api/java/util/Formatter.html
public class JavaPrintfIDPartOutputFormatter implements IDPartOutputFormatter {
+ final Logger logger =
+ LoggerFactory.getLogger(JavaPrintfIDPartOutputFormatter.class);
+
StringWriter stringwriter = new StringWriter();
private int maxOutputLength = DEFAULT_MAX_OUTPUT_LENGTH;
private String formatPattern;
- public JavaPrintfIDPartOutputFormatter () {
+ public JavaPrintfIDPartOutputFormatter() {
+ }
+
+ public JavaPrintfIDPartOutputFormatter(String formatPattern) {
+ setFormatPattern(formatPattern);
}
@Override
- public int getMaxOutputLength () {
+ public int getMaxOutputLength() {
return this.maxOutputLength;
}
- public void setMaxOutputLength (int length) {
+ @Override
+ public void setMaxOutputLength(int length) {
this.maxOutputLength = length;
}
@Override
- public String getFormatPattern () {
+ public String getFormatPattern() {
return this.formatPattern;
}
+ @Override
public void setFormatPattern(String pattern) {
- this.formatPattern = pattern;
+ if (pattern == null || pattern.trim().isEmpty()) {
+ logger.error("Format pattern cannot be null or empty.");
+ } else {
+ this.formatPattern = pattern;
+ }
}
+
@Override
public String format(String id) {
// If the formatting pattern is empty, just check length.
if (pattern == null || pattern.trim().isEmpty()) {
- isValidLength(formattedID);
+
+ if (! isValidLength(formattedID)) {
+ logger.error(
+ "Formatted ID '" + formattedID +
+ "' exceeds maximum length of " +
+ getMaxOutputLength() + " characters." +
+ "Returning ID without formatting.");
+ return id;
+ }
// Otherwise, format the ID using the pattern, then check length.
} else {
// Check whether the formatted ID exceeds the specified maximum length.
- private void isValidLength(String id) {
- if (id.length() > getMaxOutputLength()) {
- // @TODO Log error, possibly throw exception.
+ private boolean isValidLength(String id) {
+ if (id.length() <= getMaxOutputLength()) {
+ return true;
+ } else {
+ return false;
}
-
}
}
public class NoOpIDPartOutputFormatter implements IDPartOutputFormatter {
- public NoOpIDPartOutputFormatter () {
+ public NoOpIDPartOutputFormatter() {
+ }
+
+ public NoOpIDPartOutputFormatter(String formatPattern) {
+ // Do nothing.
}
@Override
- public int getMaxOutputLength () {
+ public int getMaxOutputLength() {
return Integer.MAX_VALUE;
}
- public void setMaxOutputLength (int length) {
+ @Override
+ public void setMaxOutputLength(int length) {
// Do nothing.
}
@Override
- public String getFormatPattern () {
+ public String getFormatPattern() {
return "";
}
+ @Override
public void setFormatPattern(String pattern) {
// Do nothing.
}
--- /dev/null
+package org.collectionspace.services.id.part;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// For symbols used in format patterns, see:
+// http://java.sun.com/docs/books/tutorial/i18n/format/decimalFormat.html#numberpattern
+
+public class NumericIDPartOutputFormatter implements IDPartOutputFormatter {
+
+ final Logger logger =
+ LoggerFactory.getLogger(NumericIDPartOutputFormatter.class);
+
+ private int maxOutputLength = DEFAULT_MAX_OUTPUT_LENGTH;
+ private String formatPattern;
+ private NumberFormat numberFormat;
+
+ public NumericIDPartOutputFormatter() {
+ setNumberFormat(formatPattern);
+ }
+
+ public NumericIDPartOutputFormatter(String formatPattern) {
+ setFormatPattern(formatPattern);
+ setNumberFormat(formatPattern);
+ }
+
+ @Override
+ public int getMaxOutputLength () {
+ return this.maxOutputLength;
+ }
+
+ @Override
+ public void setMaxOutputLength (int length) {
+ this.maxOutputLength = length;
+ }
+
+ @Override
+ public String getFormatPattern () {
+ return this.formatPattern;
+ }
+
+ @Override
+ public void setFormatPattern(String pattern) {
+ if (pattern == null || pattern.trim().isEmpty()) {
+ logger.error("Format pattern cannot be null or empty.");
+ } else {
+ this.formatPattern = pattern;
+ }
+ }
+
+ @Override
+ public String format(String id) {
+
+ Long l;
+ try {
+ l = (Long.parseLong(id));
+ } catch (NumberFormatException e) {
+ logger.error("Could not parse id '" + id + "' as a number.", e);
+ return id;
+ }
+
+ // Format the number using the specified format pattern.
+ String formattedID = formatLong(l);
+
+ if (! isValidLength(formattedID)) {
+ logger.error(
+ "Formatted ID '" + formattedID +
+ "' exceeds maximum length of " +
+ getMaxOutputLength() + " characters." +
+ "Returning ID without formatting.");
+ return id;
+ }
+
+ return formattedID;
+ }
+
+ public String formatLong(Long l) {
+ NumberFormat nf = getNumberFormat();
+ return nf.format(l);
+ }
+
+ public long parseAsLong(String id) {
+ Long l = 0L;
+ try {
+ Number n = getNumberFormat().parse(id);
+ l = n.longValue();
+ } catch (ParseException e) {
+ // @TODO Handle this exception
+ }
+ return l;
+ }
+
+ private NumberFormat getNumberFormat() {
+ return this.numberFormat;
+ }
+
+ private void setNumberFormat(String pattern) {
+
+ NumberFormat nf = new DecimalFormat();
+
+ // If there is no pattern specified, use a general purpose pattern.
+ if (pattern == null || pattern.trim().isEmpty()) {
+ nf = NumberFormat.getIntegerInstance();
+ // In this general purpose pattern, turn off grouping
+ // of numbers via grouping separators (such as "," or ".").
+ nf.setGroupingUsed(false);
+ // Otherwise, use the specified pattern.
+ } else {
+ if (nf instanceof DecimalFormat) {
+ try {
+ ((DecimalFormat) nf).applyPattern(pattern);
+ } catch (IllegalArgumentException e) {
+ // @TODO Handle this exception;
+ }
+ }
+
+ }
+
+ this.numberFormat = nf;
+ }
+
+ private boolean isValidLength(String id) {
+ if (id.length() <= getMaxOutputLength()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+}
+
final static private long DEFAULT_INCREMENT_BY_VALUE = 1;
private long incrementBy = DEFAULT_INCREMENT_BY_VALUE;
- // @TODO Replace the NoOp formatter with a printf formatter.
- private IDPartOutputFormatter formatter = new NoOpIDPartOutputFormatter();
+ private IDPartOutputFormatter formatter = new NumericIDPartOutputFormatter();
private IDPartValidator validator = new NumericIDPartRegexValidator();
-
public NumericSequenceIDPart() {
}
+ public NumericSequenceIDPart(String formatPattern) {
+ setOutputFormatter(new NumericIDPartOutputFormatter(formatPattern));
+ }
+
public NumericSequenceIDPart(long initial) {
setInitialID(initial);
}
setIncrementBy(incrementBy);
}
+ public NumericSequenceIDPart(String formatPattern, long initial,
+ long incrementBy) {
+ setOutputFormatter(new NumericIDPartOutputFormatter(formatPattern));
+ setInitialID(initial);
+ setIncrementBy(incrementBy);
+ }
+
@Override
public IDPartOutputFormatter getOutputFormatter() {
return this.formatter;
}
- public void setOutputFormatter (IDPartOutputFormatter formatter) {
+ public void setOutputFormatter(IDPartOutputFormatter formatter) {
this.formatter = formatter;
}
return this.validator;
}
- // newID() is implemented in superclass, SequenceIDPart.
+ @Override
+ public String newID() {
+ String newID = super.newID();
+ return getOutputFormatter().format(newID);
+ }
@Override
public boolean hasCurrentID() {
}
@Override
- public void setCurrentID(String str) {
- try {
- setCurrentID(Long.parseLong(str));
- } catch (NumberFormatException e) {
- logger.error("Could not parse current ID value as a number.", e);
+ public void setCurrentID(String id) {
+ String formatPattern = getOutputFormatter().getFormatPattern();
+ if (formatPattern == null || formatPattern.trim().isEmpty()) {
+ try {
+ setCurrentID(Long.parseLong(id));
+ } catch (NumberFormatException e) {
+ logger.error("Could not parse current ID value as a number.", e);
+ }
+ } else {
+ setCurrentID(((NumericIDPartOutputFormatter)
+ getOutputFormatter()).parseAsLong(id));
}
}
@Override
public String nextID() {
- // @TODO Rethink this approach soon, as we may not want
- // to change the current value for IDs that have been
- // provisionally issued.
- this.currentValue = this.currentValue + this.incrementBy;
- return getCurrentID();
+ return Long.toString(this.currentValue + this.incrementBy);
}
public String getIncrementBy() {
@Override
public String newID() {
if (hasCurrentID()) {
- return getOutputFormatter().format(nextID());
+ return nextID();
} else {
- return getOutputFormatter().format(getInitialID());
+ return getInitialID();
}
}
Assert.assertFalse(validator.isValid("-1"));
}
+ @Test
+ public void format() {
+
+ }
+
}
@Test
public void newIDWithSuppliedInitialValue() {
- part = new NumericSequenceIDPart(100);
+
+ part = new NumericSequenceIDPart(100);
id = part.newID();
Assert.assertEquals(id, "100");
part.setCurrentID(id);
id = part.newID();
Assert.assertEquals(id, "102");
+
+ // Tests whether default formatting has disabled grouping.
+
+ part = new NumericSequenceIDPart(12345);
+ id = part.newID();
+ Assert.assertEquals(id, "12345"); // No grouping separator; e.g. 12,345
+ part.setCurrentID(id);
+
+ id = part.newID();
+ Assert.assertEquals(id, "12346");
+ part.setCurrentID(id);
+
}
@Test
}
@Test
- public void format() {
+ public void formatWithLeadingZeros() {
+
+ // Pad at left with leading zeros up to width specified.
+ part = new NumericSequenceIDPart("000");
+ id = part.newID();
+ Assert.assertEquals(id, "001");
+ part.setCurrentID(id);
+
+ id = part.newID();
+ Assert.assertEquals(id, "002");
+ part.setCurrentID(id);
+
+ part = new NumericSequenceIDPart("000", 20, 5);
+ id = part.newID();
+ Assert.assertEquals(id, "020");
+ part.setCurrentID(id);
+
+ id = part.newID();
+ Assert.assertEquals(id, "025");
+ part.setCurrentID(id);
+
+ // Numerals with more digits than pattern do not receive padding.
+ part = new NumericSequenceIDPart("000", 5000, 1);
+ id = part.newID();
+ Assert.assertEquals(id, "5000");
+ part.setCurrentID(id);
+
+ id = part.newID();
+ Assert.assertEquals(id, "5001");
+ part.setCurrentID(id);
+ }
+
+ @Test
+ public void formatWithSeparators() {
+
+ part = new NumericSequenceIDPart("#,###", 1234567, 1);
+ id = part.newID();
+ Assert.assertEquals(id, "1,234,567");
+ part.setCurrentID(id);
+
+ id = part.newID();
+ Assert.assertEquals(id, "1,234,568");
+ part.setCurrentID(id);
}
@Test