From: Aron Roberts Date: Thu, 19 Nov 2009 03:04:11 +0000 (+0000) Subject: CSPACE-234,CSPACE-364: Ongoing work on revising interfaces for ID Parts, still in... X-Git-Url: https://git.aero2k.de/?a=commitdiff_plain;h=2e110654066eb9167462426e182bef6d006cd2aa;p=tmp%2Fjakarta-migration.git CSPACE-234,CSPACE-364: Ongoing work on revising interfaces for ID Parts, still in progress. Additional work on output formatters to facilitate customization of ID number patterns without having to create new ID parts. --- diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java index 725ae9ea4..a3cba0703 100644 --- a/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPart.java @@ -8,17 +8,28 @@ public class GregorianDateIDPart extends DateIDPart { 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); } @@ -27,11 +38,19 @@ public class GregorianDateIDPart extends DateIDPart { 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() { @@ -56,13 +75,5 @@ public class GregorianDateIDPart extends DateIDPart { 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"); - } - } diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java index 83ccf3d50..d2c89aaa6 100644 --- a/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/GregorianDateIDPartOutputFormatter.java @@ -1,11 +1,11 @@ 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; @@ -18,9 +18,17 @@ public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter 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); @@ -38,6 +46,7 @@ public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter return this.maxOutputLength; } + @Override public void setMaxOutputLength (int length) { this.maxOutputLength = length; } @@ -47,6 +56,7 @@ public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter return this.formatPattern; } + @Override public void setFormatPattern(String pattern) { if (pattern == null || pattern.trim().isEmpty()) { logger.error("Format pattern cannot be null or empty."); @@ -58,6 +68,14 @@ public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter @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)); @@ -65,14 +83,22 @@ public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter 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; @@ -88,6 +114,14 @@ public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter 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. @@ -109,23 +143,25 @@ public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter 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 + @@ -134,18 +170,10 @@ public class GregorianDateIDPartOutputFormatter implements IDPartOutputFormatter } } - 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; - } - } - } diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java index 301025168..5e3afaeaf 100644 --- a/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/IDPartOutputFormatter.java @@ -6,8 +6,13 @@ public interface IDPartOutputFormatter { 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); } diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java index ac06702c7..5e2f96870 100644 --- a/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/JavaPrintfIDPartOutputFormatter.java @@ -1,39 +1,56 @@ 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) { @@ -43,7 +60,15 @@ public class JavaPrintfIDPartOutputFormatter implements IDPartOutputFormatter { // 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 { @@ -66,11 +91,12 @@ public class JavaPrintfIDPartOutputFormatter implements IDPartOutputFormatter { // 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; } - } } diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java index 57f4c2b26..3bdfc01b8 100644 --- a/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/NoOpIDPartOutputFormatter.java @@ -2,23 +2,29 @@ package org.collectionspace.services.id.part; 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. } diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericIDPartOutputFormatter.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericIDPartOutputFormatter.java new file mode 100644 index 000000000..7600727fe --- /dev/null +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericIDPartOutputFormatter.java @@ -0,0 +1,136 @@ +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; + } + } + + +} + diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java index 88f69e845..ffbe79ccc 100644 --- a/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/NumericSequenceIDPart.java @@ -17,14 +17,16 @@ public class NumericSequenceIDPart extends SequenceIDPart { 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); } @@ -34,12 +36,19 @@ public class NumericSequenceIDPart extends SequenceIDPart { 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; } @@ -48,7 +57,11 @@ public class NumericSequenceIDPart extends SequenceIDPart { 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() { @@ -70,11 +83,17 @@ public class NumericSequenceIDPart extends SequenceIDPart { } @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)); } } @@ -94,11 +113,7 @@ public class NumericSequenceIDPart extends SequenceIDPart { @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() { diff --git a/services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java b/services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java index 882fd6b3a..767765d0e 100644 --- a/services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java +++ b/services/id/service/src/main/java/org/collectionspace/services/id/part/SequenceIDPart.java @@ -14,9 +14,9 @@ public abstract class SequenceIDPart implements IDPart, DynamicValueIDPart { @Override public String newID() { if (hasCurrentID()) { - return getOutputFormatter().format(nextID()); + return nextID(); } else { - return getOutputFormatter().format(getInitialID()); + return getInitialID(); } } diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java index 85e542567..5c02974ff 100644 --- a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericIDPartRegexValidatorTest.java @@ -24,4 +24,9 @@ public class NumericIDPartRegexValidatorTest { Assert.assertFalse(validator.isValid("-1")); } + @Test + public void format() { + + } + } diff --git a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java index 1e53bf9f8..500d2cd0f 100644 --- a/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java +++ b/services/id/service/src/test/java/org/collectionspace/services/id/part/test/NumericSequenceIDPartTest.java @@ -38,7 +38,8 @@ public class NumericSequenceIDPartTest { @Test public void newIDWithSuppliedInitialValue() { - part = new NumericSequenceIDPart(100); + + part = new NumericSequenceIDPart(100); id = part.newID(); Assert.assertEquals(id, "100"); part.setCurrentID(id); @@ -49,6 +50,18 @@ public class NumericSequenceIDPartTest { 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 @@ -67,7 +80,49 @@ public class NumericSequenceIDPartTest { } @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