From f5a3476cc38f1e20572a0451a930d233a0883531 Mon Sep 17 00:00:00 2001 From: Aron Roberts Date: Wed, 24 Jun 2009 03:48:33 +0000 Subject: [PATCH] NOJIRA Added support for getting a next ID, given a supplied instance of an ID matching a pattern. --- .../services/id/AlphabeticIDGenerator.java | 49 ++++++++++++++++- .../services/id/IDGenerator.java | 2 + .../collectionspace/services/id/IDPart.java | 5 ++ .../services/id/IDPattern.java | 55 +++++++++++++++++++ .../services/id/NumericIDGenerator.java | 23 ++++++++ .../services/id/StringIDGenerator.java | 7 +++ .../services/id/YearIDGenerator.java | 22 +++++++- .../services/id/IDPatternTest.java | 20 +++++++ 8 files changed, 177 insertions(+), 6 deletions(-) diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDGenerator.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDGenerator.java index 88b046f1e..b19690fee 100644 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDGenerator.java +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDGenerator.java @@ -23,8 +23,8 @@ // generated IDs can grow, likely as an additional parameter to be // passed to a constructor, with a default value hard-coded in the class. -// @TODO: Handle escaped characters or sequences which represent Unicode code points, -// both in the start and end characters of the sequence, and in the initial value. +// @TODO: Consider handling escaped characters or sequences which represent Unicode +// code points, both in the start and end characters of the sequence, and in the initial value. // (Example: '\u0072' for the USASCII 'r' character; see // http://www.fileformat.info/info/unicode/char/0072/index.htm) // @@ -36,6 +36,10 @@ // http://www.velocityreviews.com/forums/t367758-unescaping-unicode-code-points-in-a-java-string.html // We might also look into the (protected) source code for java.util.Properties.load() // which reads escaped Unicode values. +// +// Note also that, if the goal is to cycle through a series of alphabetic identifiers, +// such as the sequence of characters used in a particular human language, it may or may not +// be the case that any contiguous Unicode code point sequence reflects such a character sequence. // NOTE: This class currently hard-codes the assumption that the values in // alphabetic identifiers are ordered in significance from left-to-right; @@ -143,7 +147,7 @@ public class AlphabeticIDGenerator implements IDGenerator { // use Arrays.asList() to copy the initial array to a Vector.) char[] chars = initial.toCharArray(); char ch; - for (int i=0; i < chars.length; i++) { + for (int i = 0; i < chars.length; i++) { // If the character falls within the range bounded by the start and end // characters, copy it to the Vector. @@ -176,6 +180,45 @@ public class AlphabeticIDGenerator implements IDGenerator { public synchronized String getCurrentID() { return getIDString(this.currentValue); } + + // Sets the current value. + public synchronized void setCurrentID(String value) throws IllegalArgumentException { + + // @TODO Much of this code is copied from the main constructor, + // and may be ripe for refactoring. + + if (value == null || value.equals("")) { + throw new IllegalArgumentException("Initial value must not be null or empty"); + } + + // @TODO: Add a check for maximum length of the value here. + + // Store the chars in the value as Characters in a Vector, + // validating each character to identify whether it falls within + // the provided series. + // + // (Since we're performing casts from char to Character, we can't just + // use Arrays.asList() to copy the initial array to a Vector.) + char[] chars = value.toCharArray(); + char ch; + Vector v = new Vector(); + for (int i = 0; i < chars.length; i++) { + + // If the character falls within the range bounded by the start and end + // characters, copy it to the Vector. + ch = chars[i]; + if (ch >= this.startChar && ch <= this.endChar) { + v.add(new Character(ch)); + // Otherwise, we've detected a character not in the series. + } else { + throw new IllegalArgumentException("character " + "\'" + ch + "\'" + " is not valid"); + } + + } + + // Set the current value. + this.currentValue = new Vector(v); + } // Returns the next alphabetic ID in the series. // diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDGenerator.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDGenerator.java index 03b3f9bf2..5b43709d2 100644 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDGenerator.java +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDGenerator.java @@ -29,6 +29,8 @@ public interface IDGenerator { public String getInitialID(); public String getCurrentID(); + + public void setCurrentID(String value); public String getNextID(); diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPart.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPart.java index 48b4f086c..ffabe304c 100644 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPart.java +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPart.java @@ -55,6 +55,11 @@ public abstract class IDPart { return generator.getCurrentID(); } + // Sets the current value of this ID. + public synchronized void setCurrentID(String value) { + generator.setCurrentID(value); + } + // Returns the next value of this ID. public synchronized String getNextID() throws IllegalStateException { return generator.getNextID(); diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPattern.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPattern.java index 4246c39fb..cd20e8d0c 100644 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPattern.java +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPattern.java @@ -18,6 +18,9 @@ // @TODO: Add Javadoc comments +// @TODO: Catch Exceptions thrown by IDPart, then +// reflect this in the corresponding IDPatternTest class. + package org.collectionspace.services.id; import java.util.Vector; @@ -58,15 +61,63 @@ public class IDPattern { } // Returns the next value of this ID. + // + // @TODO: Throws IllegalArgumentException public synchronized String getNextID() { + // Obtain the last (least significant) IDPart, + // and call its getNextID() method, which will + // concurrently set the current value of that ID + // to the next ID. + int last = this.parts.size() - 1; + this.parts.get(last).getNextID(); + // Then call the getCurrentID() method on all of the IDParts StringBuffer sb = new StringBuffer(MAX_ID_LENGTH); + for (IDPart part : this.parts) { + sb.append(part.getCurrentID()); + } + return sb.toString(); + } + + // Returns the next value of this ID, given a + // supplied ID that entirely matches the pattern. + // + // @TODO: Throws IllegalArgumentException + public synchronized String getNextID(String value) { + + if (value == null) return value; + + Pattern pattern = Pattern.compile(getRegex()); + Matcher matcher = pattern.matcher(value); + + // If the supplied ID doesn't entirely match the pattern, + // return that same ID as the next ID. + // + // @TODO: We may wish to handle this differently, + // such as by throwing an Exception. + if (! matcher.matches()) { + return value; + } + + // Otherwise, if the supplied ID entirely matches the pattern, + // split the ID into its components and store those values in + // each of the pattern's IDparts. + IDPart currentPart; + for (int i = 1; i <= (matcher.groupCount() - 1); i++) { + currentPart = this.parts.get(i - 1); + currentPart.setCurrentID(matcher.group(i)); + } + // Obtain the last (least significant) IDPart, // and call its getNextID() method, which will // concurrently set the current value of that ID // to the next ID. + // + // @TODO: This code is duplicated in getNextID(), above, + // and thus we may want to refactor this. int last = this.parts.size() - 1; this.parts.get(last).getNextID(); // Then call the getCurrentID() method on all of the IDParts + StringBuffer sb = new StringBuffer(); for (IDPart part : this.parts) { sb.append(part.getCurrentID()); } @@ -74,8 +125,12 @@ public class IDPattern { } // Validates a provided ID against the pattern. + // + // @TODO May potentially throw at least one pattern-related exception. public synchronized boolean isValidID(String value) { + if (value == null) return false; + Pattern pattern = Pattern.compile(getRegex()); Matcher matcher = pattern.matcher(value); if (matcher.matches()) { diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDGenerator.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDGenerator.java index b8cad8bbe..12c016e30 100644 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDGenerator.java +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDGenerator.java @@ -73,6 +73,29 @@ public class NumericIDGenerator implements IDGenerator { public synchronized String getCurrentID() { return Long.toString(this.currentValue); } + + // Sets the current value. + public synchronized void setCurrentID(String value) throws IllegalArgumentException { + + // @TODO Much of this code is copied from the main constructor, + // and may be ripe for refactoring. + try { + long l = Long.parseLong(value.trim()); + if ( l < 0 ) { + throw new IllegalArgumentException("Initial ID value should be zero (0) or greater"); + } + this.currentValue = l; + this.initialValue = l; + } catch (NullPointerException e) { + throw new IllegalArgumentException("ID value should not be null"); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("ID value must be parseable as a number"); + } + + // @TODO An expedient; we may need to check the String length of the + // provided ID and calculate a maximum length here. + this.maxLength = DEFAULT_MAX_LENGTH; + } public synchronized String getNextID() throws IllegalStateException { this.currentValue++; diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDGenerator.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDGenerator.java index 02a329437..04e8e64c6 100644 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDGenerator.java +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDGenerator.java @@ -53,6 +53,13 @@ public class StringIDGenerator implements IDGenerator { public synchronized String getCurrentID() { return this.currentValue; } + + public synchronized void setCurrentID(String value) throws IllegalArgumentException { + if ( initialValue == null || initialValue == "") { + throw new IllegalArgumentException("ID value must not be null or empty"); + } + this.currentValue = value; + } public synchronized String getNextID() { return this.currentValue; diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDGenerator.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDGenerator.java index c91fc7f85..41f07f2f5 100644 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDGenerator.java +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDGenerator.java @@ -63,9 +63,8 @@ public class YearIDGenerator implements IDGenerator { throw new IllegalArgumentException("Initial ID value must not be null or empty"); } - // @TODO: Add regex-based validation here, by calling a - // to-be-added validate() method. Consider implications - // for Internationalization when doing so. + // @TODO: Add regex-based validation here, by calling isValidID(). + // Consider implications for Internationalization when doing so. this.initialValue = initialValue; this.currentValue = initialValue; @@ -83,6 +82,23 @@ public class YearIDGenerator implements IDGenerator { public synchronized String getCurrentID() { return this.currentValue; } + + // Sets the current value. + public synchronized void setCurrentID(String value) throws IllegalArgumentException { + + // @TODO This code is copied from the main constructor, + // and thus there may be an opportunity for refactoring. + + if ( value == null || value == "") { + throw new IllegalArgumentException("ID value must not be null or empty"); + } + + // @TODO: Add regex-based validation here, by calling isValidID(). + // Consider implications for Internationalization when doing so. + + this.currentValue = value; + + } // @TODO: We'll need to decide what a "next" ID means in the context of: // - An initially supplied value. diff --git a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/IDPatternTest.java b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/IDPatternTest.java index f83c522ae..3a640ffea 100644 --- a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/IDPatternTest.java +++ b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/IDPatternTest.java @@ -93,6 +93,26 @@ public class IDPatternTest extends TestCase { assertEquals("2009.1-", pattern.getNextID()); } + + public void testNextIDWithSuppliedID() { + + pattern = new IDPattern(); + pattern.add(new YearIDPart("2009")); + pattern.add(new StringIDPart(".")); + pattern.add(new NumericIDPart("1")); + assertEquals("2009.2", pattern.getNextID("2009.1")); + assertEquals("2009.3", pattern.getNextID("2009.2")); + + pattern = new IDPattern(); + pattern.add(new YearIDPart("2009")); + pattern.add(new StringIDPart(".")); + pattern.add(new NumericIDPart("1")); + pattern.add(new StringIDPart("-")); + pattern.add(new AlphabeticIDPart("a")); + assertEquals("2009.1-b", pattern.getNextID("2009.1-a")); + assertEquals("2009.3-c", pattern.getNextID("2009.3-b")); + + } public void testEmptyPartsListCurrentID() { -- 2.47.3