]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
NOJIRA Added rudimentary validation of IDs matching entire patterns, and of alphabeti...
authorAron Roberts <aron@socrates.berkeley.edu>
Wed, 24 Jun 2009 02:33:21 +0000 (02:33 +0000)
committerAron Roberts <aron@socrates.berkeley.edu>
Wed, 24 Jun 2009 02:33:21 +0000 (02:33 +0000)
sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDGenerator.java
sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDPart.java
sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPart.java
sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPattern.java
sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDGenerator.java
sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDGenerator.java
sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticIDPartTest.java
sandbox/aron/id/src/test/java/org/collectionspace/services/id/IDPatternTest.java

index 46dc4fd5c0071069c4968ba7af4c9eed5176ccce..88b046f1e26930e19e6a21f3168f7d8439f8d18a 100644 (file)
 
 // @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<Character> initialValue = new Vector<Character>();
        private Vector<Character> currentValue = new Vector<Character>();
 
-  // 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<Character>(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<Character> 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;
        }       
 }
index 9e961b3b9c9eef3d8e8b6ab4283564d201f4eb17..61cd834bcbbb86722ffb620ff776c865598d9850 100644 (file)
@@ -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));
        };
index 3cc2016c7995d4927d8f0ee12c140389f5c5b99f..48b4f086ce655827af58ee7d24f3ec8b072a6a05 100644 (file)
@@ -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();
+       }
  
 }
index c220d7cfecc4c9c21effc9191eab1910387d13a1..4246c39fb7a5e5e38df68b1c9a172142535f0693 100644 (file)
@@ -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<IDPart> parts = new Vector<IDPart>();
 
@@ -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();
+       }
  
 }
index 831ebb15a96ff0b68ed7d03fc457f42155d61c77..02a3294373b40ad36a1ec16bf7fc0648e539b26a 100644 (file)
@@ -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;
        }
        
index 8e72661a22bd264db2c4c0d8aee7895b61bcb4a4..c91fc7f851562d0beacffb7b96829da7204874b8 100644 (file)
@@ -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;
        }
        
index a20ccde4685c09abc9cf85911cb64022e30e8dcb..bbd0aa63b825491168aafd5941162af2f6a33be5 100644 (file)
@@ -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 ...
  
 }
index 4d86afac99365883f58f0ac755cf25b6df2b88c7..f83c522aeb9ca4f9d4efea90b891d42b69f8ec1f 100644 (file)
@@ -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 ...
  
 }