From: Aron Roberts Date: Wed, 24 Jun 2009 02:33:21 +0000 (+0000) Subject: NOJIRA Added rudimentary validation of IDs matching entire patterns, and of alphabeti... X-Git-Url: https://git.aero2k.de/?a=commitdiff_plain;h=72fdbe77c7aff038169ae983ff1138dde899becb;p=tmp%2Fjakarta-migration.git NOJIRA Added rudimentary validation of IDs matching entire patterns, and of alphabetic IDs parts. --- 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 46dc4fd5c..88b046f1e 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 @@ -19,17 +19,7 @@ // @TODO: Add Javadoc comments -// @TODO: The initial value determines the fixed number of characters. -// Currently, identifiers simply 'wrap' (roll over) within that fixed -// series, which is not always the desired outcome. -// -// We may also need to model cases where the number of characters -// auto-expands as the value of the most significant character -// rolls over, up to a specified maximum number of characters: -// e.g. a call to getNextID(), where the current ID is "z", -// auto-expands to "aa", and a current ID of "ZZ" auto-expands to "AAA". -// -// When doing so, we'll also need to set a maximum length to which the +// @TODO: When auto expanding, we'll need to set a maximum length to which the // generated IDs can grow, likely as an additional parameter to be // passed to a constructor, with a default value hard-coded in the class. @@ -50,7 +40,7 @@ // NOTE: This class currently hard-codes the assumption that the values in // alphabetic identifiers are ordered in significance from left-to-right; // that is, the most significant value appears in the left-most position. - + package org.collectionspace.services.id; import java.util.Collections; @@ -65,6 +55,7 @@ public class AlphabeticIDGenerator implements IDGenerator { private static final String DEFAULT_START_CHAR = "a"; private static final String DEFAULT_END_CHAR = "z"; + private static final String DEFAULT_INITIAL_VALUE = "a"; private char startChar = NULL_CHAR; private char endChar = NULL_CHAR; @@ -72,10 +63,22 @@ public class AlphabeticIDGenerator implements IDGenerator { private Vector initialValue = new Vector(); private Vector currentValue = new Vector(); - // Defaults to an 'a-z' series, representing lowercase alphabetic characters - // in the USASCII character set within Java's internal representation of - // characters (Unicode's UTF-16 encoding), if no start and end characters - // are provided for the alphabetic character sequence. + // If no start and end characters are provided for the alphabetic character + // sequence, default to an 'a-z' series, representing the lowercase alphabetic + // characters in the USASCII character set (within Java's internal + // Unicode UTF-16 representation). + // + // Additionally defaults to an initial value of "a". + public AlphabeticIDGenerator() throws IllegalArgumentException { + + this(DEFAULT_START_CHAR, DEFAULT_END_CHAR, DEFAULT_INITIAL_VALUE); + + } + + // If no start and end characters are provided for the alphabetic character + // sequence, default to an 'a-z' series, representing the lowercase alphabetic + // characters in the USASCII character set (within Java's internal + // Unicode UTF-16 representation). public AlphabeticIDGenerator(String initial) throws IllegalArgumentException { this(DEFAULT_START_CHAR, DEFAULT_END_CHAR, initial); @@ -92,15 +95,16 @@ public class AlphabeticIDGenerator implements IDGenerator { "Start character in the alphabetic series must not be null or empty"); } - // @TODO The next two statements will need to be revised to handle escaped - // representations of characters outside the USASCII character set. - if (seriesStart.length() > 1) { + if (seriesStart.length() == 1) { + this.startChar = seriesStart.charAt(0); + } else if (false) { + // Handle representations of Unicode code points here + } else { throw new IllegalArgumentException( - "Start character in the alphabetic series must be exactly one character in length"); + "Start character must be one character in length"); + // "Start character must be one character in length or a Unicode value such as '\u0000'"); } - this.startChar = seriesStart.charAt(0); - // Validate and store the end character in the alphabetic series. if (seriesEnd == null || seriesEnd.equals("")) { @@ -108,18 +112,19 @@ public class AlphabeticIDGenerator implements IDGenerator { "End character in the alphabetic series must not be null or empty"); } - // @TODO The next two statements will need to be revised to handle escaped - // representations of characters outside the USASCII character set. - if (seriesEnd.length() > 1) { + if (seriesEnd.length() == 1) { + this.endChar = seriesEnd.charAt(0); + } else if (false) { + // Handle representations of Unicode code points here + } else { throw new IllegalArgumentException( - "End character in the alphabetic series must be exactly one character in length"); + "End character must be one character in length"); + // "End character must be one character in length or a Unicode value such as '\u0000'"); } - - this.endChar = seriesEnd.charAt(0); if (this.endChar <= this.startChar) { throw new IllegalArgumentException( - "End (last) character in an alphabetic series must be greater than the start character"); + "End (last) character in the alphabetic series must be greater than the start character"); } // Validate and store the initial value of this identifier. @@ -130,27 +135,28 @@ public class AlphabeticIDGenerator implements IDGenerator { // @TODO: Add a check for maximum length of the initial value here. - // Store the chars in the initial value as Characters in a Vector. + // Store the chars in the initial 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 = initial.toCharArray(); + char ch; for (int i=0; i < chars.length; i++) { - this.initialValue.add(new Character(chars[i])); - } - - // Validate that each of the characters in the initial value - // falls within the provided series. - for ( Character ch : this.initialValue ) { - - if (ch.charValue() >= this.startChar && ch.charValue() <= this.endChar) { - continue; + + // 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) { + this.initialValue.add(new Character(ch)); // Otherwise, we've detected a character not in the series. } else { throw new IllegalArgumentException("character " + "\'" + ch + "\'" + " is not valid"); } - - } // end 'for' loop - + + } + // Initialize the current value from the initial value. this.currentValue = new Vector(this.initialValue); @@ -172,22 +178,30 @@ public class AlphabeticIDGenerator implements IDGenerator { } // Returns the next alphabetic ID in the series. - public synchronized String getNextID() { - + // + // Currently, the number of characters auto-expands as the + // value of the most significant character rolls over. + // E.g. a call to getNextID(), where the current ID is "z", + // auto-expands to "aa", and "ZZ" auto-expands to "AAA". + // + // See the TODOs at the top of this class for additional + // functionality that needs to be implemented. + public synchronized String getNextID() { + // Get next values for each character, from right to left // (least significant to most significant). boolean expandIdentifier = false; int size = this.currentValue.size(); - char c; + char ch; for (int i = (size - 1); i >= 0; i--) { - c = this.currentValue.get(i).charValue(); + ch = this.currentValue.get(i).charValue(); - // When reaching the maximum value for any character position, - // 'roll over' to the minimum value for that position. - if (c == this.endChar) { + // When we reach the maximum value for any character, + // 'roll over' to the minimum value in our character range. + if (ch == this.endChar) { this.currentValue.set(i, Character.valueOf(this.startChar)); - // If this roll over occurs in the most significant value, + // If this rollover occurs in the most significant value, // set a flag to later expand the size of the identifier. // // @TODO: Set another flag to enable or disable this behavior, @@ -195,9 +209,11 @@ public class AlphabeticIDGenerator implements IDGenerator { if (i == 0) { expandIdentifier = true; } + // When we reach the most significant character whose value + // doesn't roll over, increment that character and exit the loop. } else { - c++; - this.currentValue.set(i, Character.valueOf(c)); + ch++; + this.currentValue.set(i, Character.valueOf(ch)); i = -1; break; } @@ -205,8 +221,8 @@ public class AlphabeticIDGenerator implements IDGenerator { } // If we are expanding the size of the identifier, insert a new - // value at the most significant character position, sliding other - // values to the right. + // value at the most significant (leftmost) character position, + // sliding other values to the right. if (expandIdentifier) { this.currentValue.add(0, Character.valueOf(this.startChar)); } @@ -215,9 +231,8 @@ public class AlphabeticIDGenerator implements IDGenerator { } - // Returns a String representation of the contents of a Vector, - // in the form of an identifier (e.g. each Character's String value - // is appended to the next). + // Returns a String representation of the ID, by appending + // the String values of each character in the Vector. public synchronized String getIDString(Vector v) { StringBuffer sb = new StringBuffer(); for ( Character ch : v ) { @@ -243,8 +258,13 @@ public class AlphabeticIDGenerator implements IDGenerator { } public synchronized String getRegex() { - // @TODO: This method is stubbed out; it needs to be implemented. - String regex = "(" + "\\*" + ")"; + // @TODO: May need to constrain the number of alphabetic characters based + // on a maximum value, TBA. Currently, this regex simply matches sequences + // of one or more characters. + String regex = + "(" + "[" + + String.valueOf(this.startChar) + "-" + String.valueOf(this.endChar) + + "]+" + ")"; return regex; } } diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDPart.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDPart.java index 9e961b3b9..61cd834bc 100644 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDPart.java +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDPart.java @@ -22,6 +22,10 @@ package org.collectionspace.services.id; public class AlphabeticIDPart extends IDPart { + public AlphabeticIDPart() { + super(new AlphabeticIDGenerator()); + }; + public AlphabeticIDPart(String baseVal) { super(new AlphabeticIDGenerator(baseVal)); }; 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 3cc2016c7..48b4f086c 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 @@ -70,5 +70,10 @@ public abstract class IDPart { public synchronized boolean isValidID(String value) throws IllegalArgumentException { return generator.isValidID(value); } + + // Returns a regular expression pattern used for ID validation. + public synchronized String getRegex() { + return generator.getRegex(); + } } 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 c220d7cfe..4246c39fb 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 @@ -22,9 +22,12 @@ package org.collectionspace.services.id; import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class IDPattern { - final static int MAX_ID_LENGTH = 30; + final static int MAX_ID_LENGTH = 50; private Vector parts = new Vector(); @@ -58,17 +61,38 @@ public class IDPattern { public synchronized String getNextID() { StringBuffer sb = new StringBuffer(MAX_ID_LENGTH); // Obtain the last (least significant) IDPart, - // call its getNextID() method, and + // 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(); - // lastPart.getNextID(); - // this.parts.set(last, lastPart); + // Then call the getCurrentID() method on all of the IDParts for (IDPart part : this.parts) { sb.append(part.getCurrentID()); } return sb.toString(); } - // public boolean validate() {}; + // Validates a provided ID against the pattern. + public synchronized boolean isValidID(String value) { + + Pattern pattern = Pattern.compile(getRegex()); + Matcher matcher = pattern.matcher(value); + if (matcher.matches()) { + return true; + } else { + return false; + } + + } + + // Returns a regular expression to validate this ID. + public synchronized String getRegex() { + StringBuffer sb = new StringBuffer(); + for (IDPart part : this.parts) { + sb.append(part.getRegex()); + } + return sb.toString(); + } } 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 831ebb15a..02a329437 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 @@ -24,6 +24,7 @@ package org.collectionspace.services.id; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; public class StringIDGenerator implements IDGenerator { @@ -74,7 +75,21 @@ public class StringIDGenerator implements IDGenerator { } public synchronized String getRegex() { - String regex = "(" + this.initialValue + ")"; + + String initial = this.initialValue; + + // Escape or otherwise modify various characters that have + // significance in regular expressions. + // + // @TODO Test these thoroughly, add processing of more + // special characters as needed. + + // Escape un-escaped period/full stop characters. + Pattern pattern = Pattern.compile("([^\\\\]{0,1})\\."); + Matcher matcher = pattern.matcher(initial); + String escapedInitial = matcher.replaceAll("$1\\\\."); + + String regex = "(" + escapedInitial + ")"; return regex; } 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 8e72661a2..c91fc7f85 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 @@ -118,7 +118,7 @@ public class YearIDGenerator implements IDGenerator { public synchronized String getRegex() { // NOTE: Currently hard-coded to accept only a range of // four-digit Gregorian Calendar year dates. - String regex = "(\\d\\d\\d\\d)"; + String regex = "(\\d{4})"; return regex; } diff --git a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticIDPartTest.java b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticIDPartTest.java index a20ccde46..bbd0aa63b 100644 --- a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticIDPartTest.java +++ b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticIDPartTest.java @@ -35,20 +35,28 @@ public class AlphabeticIDPartTest extends TestCase { assertEquals("y", part.getNextID()); assertEquals("z", part.getNextID()); +} + + public void testGetNextIDLowercase2Chars() { + part = new AlphabeticIDPart("aa"); assertEquals("ab", part.getNextID()); assertEquals("ac", part.getNextID()); - part = new AlphabeticIDPart("ay"); - assertEquals("az", part.getNextID()); - assertEquals("ba", part.getNextID()); - assertEquals("bb", part.getNextID()); - part = new AlphabeticIDPart("zx"); assertEquals("zy", part.getNextID()); assertEquals("zz", part.getNextID()); } + + public void testGetNextIDLowercase2CharsRolloverFirst() { + + part = new AlphabeticIDPart("ay"); + assertEquals("az", part.getNextID()); + assertEquals("ba", part.getNextID()); + assertEquals("bb", part.getNextID()); + + } public void testGetNextIDUppercase() { @@ -60,21 +68,29 @@ public class AlphabeticIDPartTest extends TestCase { assertEquals("Y", part.getNextID()); assertEquals("Z", part.getNextID()); +} + + public void testGetNextIDUppercase2Chars() { + part = new AlphabeticIDPart("A", "Z", "AA"); assertEquals("AB", part.getNextID()); assertEquals("AC", part.getNextID()); - part = new AlphabeticIDPart("A", "Z", "AY"); - assertEquals("AZ", part.getNextID()); - assertEquals("BA", part.getNextID()); - assertEquals("BB", part.getNextID()); - part = new AlphabeticIDPart("A", "Z", "ZX"); assertEquals("ZY", part.getNextID()); assertEquals("ZZ", part.getNextID()); } + public void testGetNextIDUppercase2CharsRolloverFirst() { + + part = new AlphabeticIDPart("A", "Z", "AY"); + assertEquals("AZ", part.getNextID()); + assertEquals("BA", part.getNextID()); + assertEquals("BB", part.getNextID()); + + } + public void testResetLowercase() { part = new AlphabeticIDPart("zx"); @@ -185,6 +201,48 @@ public class AlphabeticIDPartTest extends TestCase { } } + public void testIsValidIDDefaultSeries() { + + part = new AlphabeticIDPart(); + + assertTrue(part.isValidID("a")); + assertTrue(part.isValidID("z")); + + assertFalse(part.isValidID("A")); + assertFalse(part.isValidID("123")); + + } + + public void testIsValidIDConstrainedLowerCaseSeries() { + + part = new AlphabeticIDPart("a", "f", "a"); + + assertTrue(part.isValidID("a")); + assertTrue(part.isValidID("b")); + assertTrue(part.isValidID("f")); + + assertFalse(part.isValidID("g")); + assertFalse(part.isValidID("z")); + assertFalse(part.isValidID("A")); + assertFalse(part.isValidID("123")); + + } + + public void testIsValidIDConstrainedUppercaseSeries() { + + part = new AlphabeticIDPart("A", "F", "A"); + + assertTrue(part.isValidID("A")); + assertTrue(part.isValidID("B")); + assertTrue(part.isValidID("F")); + + assertFalse(part.isValidID("G")); + assertFalse(part.isValidID("Z")); + assertFalse(part.isValidID("a")); + assertFalse(part.isValidID("123")); + + } + // @TODO: Add more tests of boundary conditions, exceptions ... } 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 4d86afac9..f83c522ae 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 @@ -101,6 +101,56 @@ public class IDPatternTest extends TestCase { } + public void testIsValidIDYearPattern() { + + pattern = new IDPattern(); + pattern.add(new YearIDPart("2009")); + + assertTrue(pattern.isValidID("2009")); + assertTrue(pattern.isValidID("5555")); + + assertFalse(pattern.isValidID("456")); + assertFalse(pattern.isValidID("10000")); + + } + + + public void testGetRegex() { + + pattern = new IDPattern(); + pattern.add(new YearIDPart("2009")); + pattern.add(new StringIDPart(".")); + pattern.add(new NumericIDPart("1")); + assertEquals("(\\d{4})(\\.)(\\d{1,6})", pattern.getRegex()); + + } + + public void testIsValidIDYearSeparatorItemPattern() { + + pattern = new IDPattern(); + pattern.add(new YearIDPart("2009")); + pattern.add(new StringIDPart(".")); + pattern.add(new NumericIDPart("1")); + + assertTrue(pattern.isValidID("2009.1")); + assertTrue(pattern.isValidID("5555.55")); + + assertFalse(pattern.isValidID("456.1")); + assertFalse(pattern.isValidID("2009-1")); + assertFalse(pattern.isValidID("2009.a")); + assertFalse(pattern.isValidID("2009-a")); + assertFalse(pattern.isValidID("non-pattern conforming text")); + + pattern = new IDPattern(); + pattern.add(new YearIDPart("2009")); + pattern.add(new StringIDPart("ZZ.AND.")); + pattern.add(new NumericIDPart("1")); + + assertTrue(pattern.isValidID("2009ZZ.AND.1")); + assertFalse(pattern.isValidID("2009ZZ-AND-1")); + + } + // @TODO: Add more tests of boundary conditions, exceptions ... }