From 5e64b769d83036b6fcfd913fa92efd335c6a9d0b Mon Sep 17 00:00:00 2001 From: Aron Roberts Date: Sun, 21 Jun 2009 15:46:15 +0000 Subject: [PATCH] NOJIRA Added additional ID Parts and new methods in IDPart, removed dependencies on Apache Commons Id, added rudimentary ID Pattern. --- sandbox/aron/id/pom.xml | 11 +- .../services/id/AlphabeticGenerator.java | 212 ------------------ .../services/id/AlphabeticIDGenerator.java | 178 +++++++++++++++ .../services/id/AlphabeticIDPart.java | 29 +++ .../services/id/AlphabeticSeriesIDPart.java | 33 --- .../services/id/IDGenerator.java | 31 +++ .../collectionspace/services/id/IDPart.java | 64 +++--- .../services/id/IDPattern.java | 68 ++++++ .../services/id/NumericIDGenerator.java | 63 ++++++ .../services/id/NumericIDPart.java | 36 +++ .../services/id/SeriesIDPart.java | 71 ------ .../services/id/StringIDGenerator.java | 61 +++++ .../services/id/StringIDPart.java | 29 +++ .../services/id/YearIDGenerator.java | 82 +++++++ .../services/id/YearIDPart.java | 34 +++ .../services/id/AlphabeticGeneratorTest.java | 198 ---------------- .../services/id/AlphabeticIDPartTest.java | 202 +++++++++++++++++ .../id/AlphabeticSeriesIDPartTest.java | 44 ---- .../services/id/IDPatternTest.java | 62 +++++ .../services/id/NumericIDPartTest.java | 101 +++++++++ .../services/id/StringIDPartTest.java | 76 +++++++ .../services/id/YearIDPartTest.java | 94 ++++++++ .../third-party/commons-id-1.0-SNAPSHOT.jar | Bin 55341 -> 0 bytes .../id/third-party/install-third-party.sh | 8 - 24 files changed, 1190 insertions(+), 597 deletions(-) delete mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticGenerator.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDGenerator.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDPart.java delete mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticSeriesIDPart.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDGenerator.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPattern.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDGenerator.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDPart.java delete mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/SeriesIDPart.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDGenerator.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDPart.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDGenerator.java create mode 100644 sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDPart.java delete mode 100644 sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticGeneratorTest.java create mode 100644 sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticIDPartTest.java delete mode 100644 sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticSeriesIDPartTest.java create mode 100644 sandbox/aron/id/src/test/java/org/collectionspace/services/id/IDPatternTest.java create mode 100644 sandbox/aron/id/src/test/java/org/collectionspace/services/id/NumericIDPartTest.java create mode 100644 sandbox/aron/id/src/test/java/org/collectionspace/services/id/StringIDPartTest.java create mode 100644 sandbox/aron/id/src/test/java/org/collectionspace/services/id/YearIDPartTest.java delete mode 100644 sandbox/aron/id/third-party/commons-id-1.0-SNAPSHOT.jar delete mode 100644 sandbox/aron/id/third-party/install-third-party.sh diff --git a/sandbox/aron/id/pom.xml b/sandbox/aron/id/pom.xml index 60292caa1..1fde68d0c 100644 --- a/sandbox/aron/id/pom.xml +++ b/sandbox/aron/id/pom.xml @@ -7,7 +7,11 @@ 0.1-SNAPSHOT id http://maven.apache.org - + + + UTF-8 + + org.apache.maven.plugins @@ -20,11 +24,6 @@ - - org.apache.commons - commons-id - 1.0-SNAPSHOT - junit junit diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticGenerator.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticGenerator.java deleted file mode 100644 index 0633ff100..000000000 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticGenerator.java +++ /dev/null @@ -1,212 +0,0 @@ - /* - * AlphabeticGenerator - * - *

An identifier generator that generates an incrementing ID from any composite - * of the USASCII character sequences 'A-Z' and 'a-z', as a String object.

- * - *

The wrap property determines whether or not the sequence wraps - * when it reaches the largest value that can be represented in size. - * If wrap is false and the the maximum representable - * value is exceeded, an IllegalStateException is thrown

- * - * Copyright 2009 Regents of the University of California - * - * Licensed under the Educational Community License (ECL), Version 2.0. - * You may not use this file except in compliance with this License. - * - * You may obtain a copy of the ECL 2.0 License at - * https://source.collectionspace.org/collection-space/LICENSE.txt - * - * @author $Author$ - * @version $Revision$ - * $Date$ - */ - -// @TODO: The initial value determines the fixed number of characters. -// We may also need to model cases where the number of characters -// increases as values roll over, up to a specified maximum number of -// characters; e.g. "z" becomes "aa", and "ZZ" becomes "AAA". - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.collectionspace.services.id; - -import org.apache.commons.id.AbstractStringIdentifierGenerator; -import java.io.Serializable; - -public class AlphabeticGenerator extends AbstractStringIdentifierGenerator - implements Serializable { - - /** - * serialVersionUID is the serializable UID for the binary version of the class. - */ - // private static final long serialVersionUID = 20060120L; // @TODO ReplaceMe! - - /** - * Should the counter wrap. - */ - private boolean wrapping = true; - - /** - * The counter. - */ - private char[] count = null; - private char[] initialcount = null; - - /** - * 'Z' and 'z' chars - */ - private static final char LOWERCASE_Z_CHAR = 'z'; - private static final char UPPERCASE_Z_CHAR = 'Z'; - - /** - * Constructor with a default size for the alphanumeric identifier. - * - * @param wrap should the factory wrap when it reaches the maximum - * value (or throw an exception) - */ - public AlphabeticGenerator(boolean wrap) { - this(wrap, DEFAULT_ALPHANUMERIC_IDENTIFIER_SIZE); - } - - /** - * Constructor. - * - * @param wrap should the factory wrap when it reaches the maximum - * value (or throw an exception) - * @param size the size of the identifier - */ - public AlphabeticGenerator(boolean wrap, int size) { - super(); - this.wrapping = wrap; - if (size < 1) { - throw new IllegalArgumentException("The size must be at least one"); - } - this.count = new char[size]; - - // Initialize the contents of the identifier's character array - for (int i = 0; i < size; i++) { - count[i] = ' '; // space - } - } - - /** - * Construct with a counter, that will start at the specified - * alphanumeric value.

- * - * @param wrap should the factory wrap when it reaches the maximum - * value (or throw an exception) - * @param initialValue the initial value to start at - */ - public AlphabeticGenerator(boolean wrap, String initialValue) { - super(); - this.wrapping = wrap; - - if ( initialValue == null ) { - throw new IllegalArgumentException("Initial value must not be null"); - } - - if ( initialValue == "" ) { - throw new IllegalArgumentException("Initial value must not be empty"); - } - - this.count = initialValue.toCharArray(); - - // Validate each of the characters in the initial value - // against ranges of valid values. - for (int i = 0; i < this.count.length; i++) { - char ch = this.count[i]; - if (ch >= 'A' && ch <= 'Z') continue; - if (ch >= 'a' && ch <= 'z') continue; - - throw new IllegalArgumentException( - "character " + this.count[i] + " is not valid"); - } - - // Store the initial character array - this.initialcount = this.count; - } - - public long maxLength() { - return this.count.length; - } - - public long minLength() { - return this.count.length; - } - - /** - * Getter for property wrap. - * - * @return true if this generator is set up to wrap. - * - */ - public boolean isWrap() { - return wrapping; - } - - /** - * Sets the wrap property. - * - * @param wrap value for the wrap property - * - */ - public void setWrap(boolean wrap) { - this.wrapping = wrap; - } - - /** - * Returns the (constant) size of the strings generated by this generator. - * - * @return the size of generated identifiers - */ - public int getSize() { - return this.count.length; - } - - public synchronized String nextStringIdentifier() { - - // Get next values for each character from right to left - for (int i = count.length - 1; i >= 0; i--) { - switch (count[i]) { - - case LOWERCASE_Z_CHAR: // z - if (i == 0 && !wrapping) { - throw new IllegalStateException - ("The maximum number of identifiers has been reached"); - } - count[i] = 'a'; - break; - - case UPPERCASE_Z_CHAR: // Z - if (i == 0 && !wrapping) { - throw new IllegalStateException - ("The maximum number of identifiers has been reached"); - } - count[i] = 'A'; - break; - - default: - count[i]++; - i = -1; - break; - } - } - return new String(count); - } -} 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 new file mode 100644 index 000000000..cd7f105ce --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDGenerator.java @@ -0,0 +1,178 @@ +/* + * AlphabeticIDGenerator + * + *

An identifier generator that generates an incrementing ID from any composite + * of the USASCII character sequences 'A-Z' and 'a-z', as a String object.

+ * + *

The wrap property determines whether or not the sequence wraps + * when it reaches the largest value that can be represented in size. + * If wrap is false and the the maximum representable + * value is exceeded, an IllegalStateException is thrown

+ * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +// @TODO: The initial value determines the fixed number of characters. +// We may also need to model cases where the number of characters +// increases as values roll over, up to a specified maximum number of +// characters; e.g. "z" becomes "aa", and "ZZ" becomes "AAA". When +// doing so, we'll also need to set a maximum length to which the +// generated IDs can grow. + +// @TODO: This class is hard-coded to use two series within the +// USASCII character set. +// +// With some minor refactoring, we could draw upon minimum and maximum +// character values for a wide range of arbitrary character sets. + +// Some code and algorithms in the current iteration of this class +// were adapted from the org.apache.commons.Id package, and thus +// the relevant licensing terms are included here: + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package org.collectionspace.services.id; + +public class AlphabeticIDGenerator implements IDGenerator { + + private static final char LOWERCASE_Z_CHAR = 'z'; + private static final char UPPERCASE_Z_CHAR = 'Z'; + + private char[] initialValue = null; + private char[] currentValue = null; + + public AlphabeticIDGenerator(String initialValue) throws IllegalArgumentException { + + if ( initialValue == null ) { + throw new IllegalArgumentException("Initial value must not be null"); + } + + if ( initialValue == "" ) { + throw new IllegalArgumentException("Initial value must not be empty"); + } + + char[] charsToValidate = initialValue.toCharArray(); + + // Validate each of the characters in the initial value + // against ranges of valid values. + for (int i = 0; i < charsToValidate.length; i++) { + + char ch = charsToValidate[i]; + + // If the value of the current character matches a character + // in the uppercase ('A-Z') or lowercase ('a-z') series + // in the USASCII character set, that character has a valid value, + // so we can skip to checking the next character. + if (ch >= 'A' && ch <= 'Z') continue; + if (ch >= 'a' && ch <= 'z') continue; + + // Otherwise, we've detected a character not in those series. + throw new IllegalArgumentException( + "character " + charsToValidate[i] + " is not valid"); + + } // end 'for' loop + + // Store the initial character array + this.initialValue = charsToValidate; + this.currentValue = charsToValidate; + + } + + public synchronized void reset() { + try { + // TODO: Investigate using different methods to perform this copying, + // such as clone. See "Java Practices - Copy an Array" + // + // char [] copy = (char []) initialValue.clone(); + // this.currentValue = copy; + // System.arraycopy( + // this.initialValue, 0, this.currentValue, 0, this.initialValue.length ); + for ( int i = 0; i < this.initialValue.length; ++i ) { + this.currentValue[i] = this.initialValue[i]; + } + // If copying would cause access of data outside array bounds. + } catch (IndexOutOfBoundsException iobe) { + // For experimentation - do nothing here at this time. + // If an element in the source array could not be stored into + // the destination array because of a type mismatch. + } catch (ArrayStoreException ase) { + // For experimentation - do nothing here at this time. + // If either source or destination is null. + } catch (NullPointerException npe) { + // For experimentation - do nothing here at this time. + } + } + + public synchronized String getInitialID() { + return new String(this.initialValue); + } + + public synchronized String getCurrentID() { + return new String(this.currentValue); + } + + public synchronized String getNextID() { + + // Get next values for each character, from right to left + // (least significant to most significant). + // + // When reaching the maximum value for any character position, + // 'roll over' to the minimum value for that position. + for (int i = (this.currentValue.length - 1); i >= 0; i--) { + + switch (this.currentValue[i]) { + + case LOWERCASE_Z_CHAR: // z + if (i == 0) { + throw new IllegalStateException( + "The maximum number of IDs has been reached"); + } + this.currentValue[i] = 'a'; + break; + + case UPPERCASE_Z_CHAR: // Z + if (i == 0) { + throw new IllegalStateException( + "The maximum number of IDs has been reached"); + } + this.currentValue[i] = 'A'; + break; + + default: + this.currentValue[i]++; + i = -1; + break; + + } // end switch + + } // end 'for' loop + + return new String(currentValue); + + } + +} 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 new file mode 100644 index 000000000..8f97a2687 --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticIDPart.java @@ -0,0 +1,29 @@ + /* + * AlphabeticIDPart + * + * Models a part of an identifier (ID) whose values are an alphabetic series. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +package org.collectionspace.services.id; + +public class AlphabeticIDPart extends IDPart { + + public AlphabeticIDPart(String baseVal) { + super(new AlphabeticIDGenerator(baseVal)); + }; + +} diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticSeriesIDPart.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticSeriesIDPart.java deleted file mode 100644 index 26e32a4f1..000000000 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/AlphabeticSeriesIDPart.java +++ /dev/null @@ -1,33 +0,0 @@ - /* - * AlphabeticSeriesIDPart - * - * Models a part of an identifier (ID) whose value is alphabetic, - * and increments within a series of uppercase and/or lowercase values - * in the USASCII character sequence. - * - * Copyright 2009 Regents of the University of California - * - * Licensed under the Educational Community License (ECL), Version 2.0. - * You may not use this file except in compliance with this License. - * - * You may obtain a copy of the ECL 2.0 License at - * https://source.collectionspace.org/collection-space/LICENSE.txt - * - * @author $Author$ - * @version $Revision$ - * $Date$ - */ - -package org.collectionspace.services.id; - -import org.apache.commons.id.StringIdentifierGenerator; - -public class AlphabeticSeriesIDPart extends SeriesIDPart { - - public AlphabeticSeriesIDPart(String baseVal) { - // Store the appropriate Alphabetic ID generator and the base value for this part - // Value 'false' refers to the NO_WRAP behavior of the StringIdentifierGenerator. - super(new AlphabeticGenerator(false, baseVal), baseVal); - }; - -} 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 new file mode 100644 index 000000000..e114592fa --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDGenerator.java @@ -0,0 +1,31 @@ + /* + * IDGenerator + * + * Interface for a generator class that returns IDs. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +package org.collectionspace.services.id; + +public interface IDGenerator { + + public void reset(); + + public String getInitialID(); + + public String getCurrentID(); + + 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 90ac7a45b..32bcb7511 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 @@ -1,8 +1,16 @@ /* * IDPart * - * Models a part of an identifier (ID), such as (for instance) an incrementing - * numeric or alphabetic value, a date value, or a static separator. + *

Models a part of an identifier (ID).

+ * + *

Some representative examples of data that can be + * managed within IDParts include:

+ * + *
    + *
  • Incrementing numeric or alphabetic values
  • + *
  • Date values
  • + *
  • Static separators
  • + *
* * Copyright 2009 Regents of the University of California * @@ -16,39 +24,45 @@ * @version $Revision$ * $Date$ */ - -package org.collectionspace.services.id; -import org.apache.commons.id.StringIdentifierGenerator; +// @TODO: Add Javadoc comments + +package org.collectionspace.services.id; public abstract class IDPart { - // Flags to identify whether series-based identifiers - // wrap to their initial values, after the last value - // in the series is reached. - final boolean WRAP = true; - final boolean NO_WRAP = false; - - // An identifier generator - protected StringIdentifierGenerator generator; + // A generator for the types of IDs that are generated by this part. + // This generator is passed in at construction time. + protected IDGenerator generator; // Constructor - public IDPart(StringIdentifierGenerator idGenerator) { - setGenerator(idGenerator); + public IDPart(IDGenerator idGenerator) { + this.generator = idGenerator; } - // Sets the identifier generator - protected void setGenerator(StringIdentifierGenerator idGenerator) { - if (idGenerator != null) { - generator = idGenerator; - } + // Resets the ID to its initial value. + public synchronized void reset() { + generator.reset(); + } + + // Returns the initial value of this ID. + public synchronized String getInitialID() { + return generator.getInitialID(); } - // Gets the next identifier - public String nextIdentifier() { - // @TODO: Add Exception-handling here ... - return generator.nextStringIdentifier(); - }; + // Returns the current value of this ID. + public synchronized String getCurrentID() { + return generator.getCurrentID(); + } + + // Returns the next value of this ID. + public synchronized String getNextID() { + try { + return generator.getNextID(); + } catch (IllegalStateException e) { + throw e; + } + } // public boolean validate() {}; 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 new file mode 100644 index 000000000..e94df2786 --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/IDPattern.java @@ -0,0 +1,68 @@ + /* + * IDPattern + * + *

Models an identifier (ID), which consists of multiple IDParts.

+ * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +package org.collectionspace.services.id; + +import java.util.Vector; + +public class IDPattern { + + final static int MAX_ID_LENGTH = 30; + + private Vector parts = new Vector(); + + // Constructor + public IDPattern() { + } + + // Constructor + public IDPattern(Vector partsList) { + if (partsList != null) { + this.parts = partsList; + } + } + + public void add(IDPart part) { + if (part != null) { + this.parts.add(part); + } + } + + // Returns the current value of this ID. + public synchronized String getCurrentID() { + 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. + public synchronized String getNextID() { + StringBuffer sb = new StringBuffer(MAX_ID_LENGTH); + for (IDPart part : this.parts) { + sb.append(part.getCurrentID()); + } + return sb.toString(); + } + + // public boolean validate() {}; + +} 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 new file mode 100644 index 000000000..88dbfbca2 --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDGenerator.java @@ -0,0 +1,63 @@ +/* + * NumericIDGenerator + * + *

An identifier generator that generates an incrementing ID as a + * series of numeric values, beginning from an initial value.

+ * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +// @TODO: Need to set and enforce maximum value. + +package org.collectionspace.services.id; + +public class NumericIDGenerator implements IDGenerator { + + private long initialValue = 0; + private long currentValue = 0; + + public NumericIDGenerator(String initialValue) throws IllegalArgumentException { + try { + long l = Long.parseLong(initialValue.trim()); + if ( l < 0 ) { + throw new IllegalArgumentException("Initial value should be zero (0) or greater"); + } + this.currentValue = l; + this.initialValue = l; + } catch (NullPointerException e) { + throw new IllegalArgumentException("Initial value should not be null"); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Initial value must be parseable as a number"); + } + } + + public synchronized void reset() { + this.currentValue = this.initialValue; + } + + public synchronized String getInitialID() { + return Long.toString(this.initialValue); + } + + public synchronized String getCurrentID() { + return Long.toString(this.currentValue); + } + + public synchronized String getNextID() { + this.currentValue++; + return Long.toString(this.currentValue); + } + +} diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDPart.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDPart.java new file mode 100644 index 000000000..5d68030fc --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/NumericIDPart.java @@ -0,0 +1,36 @@ + /* + * NumericIDPart + * + * Models a part of an identifier (ID) whose values come from an + * incrementing numeric series, with those values represented as + * String objects. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +package org.collectionspace.services.id; + +public class NumericIDPart extends IDPart { + + public NumericIDPart(String baseVal) { + // Store the appropriate Numeric ID generator and the base value for this part. + + // @TODO: Determine how to handle the NumberFormatException that will be thrown + // from parseLong "if the string does not contain a parsable long." We may + // need a shim to perform this conversion prior to setting up the generator. + super(new NumericIDGenerator(baseVal)); + }; + +} diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/SeriesIDPart.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/SeriesIDPart.java deleted file mode 100644 index 9c92f6c1d..000000000 --- a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/SeriesIDPart.java +++ /dev/null @@ -1,71 +0,0 @@ - /* - * SeriesIDPart - * - * Models a part of an identifier (ID) whose values are part of a series, - * such as (for instance) an incrementing numeric or alphabetic value. - * Values begin at an initial (base) value - * - * Copyright 2009 Regents of the University of California - * - * Licensed under the Educational Community License (ECL), Version 2.0. - * You may not use this file except in compliance with this License. - * - * You may obtain a copy of the ECL 2.0 License at - * https://source.collectionspace.org/collection-space/LICENSE.txt - * - * @author $Author$ - * @version $Revision$ - * $Date$ - */ - - package org.collectionspace.services.id; - -import org.apache.commons.id.StringIdentifierGenerator; - -public abstract class SeriesIDPart extends IDPart { - - protected String baseValue = ""; - protected String lastIdGenerated = null; - - public SeriesIDPart(StringIdentifierGenerator idGenerator, String baseVal) { - // Store the identifier generator and base value, - // and set the current value to the base value - super(idGenerator); - setBaseValue(baseVal); - } - - // Store the base value - protected void setBaseValue(String baseVal) { - // @TODO: Throw an Exception if the base value is null. - if (baseVal != null) { - baseValue = baseVal; - } - }; - - // Get the base value - public String getBaseValue() { - return baseValue; - }; - - // Get the next identifier in series - public String nextIdentifier() { - // @TODO: Add Exception-handling here ... - // If no identifier has ever been generated, - // or if the value of the last identifier was reset, - // return the base value. - if (lastIdGenerated == null) { - lastIdGenerated = baseValue; - return lastIdGenerated; - // Otherwise, return the next value in the series. - } else { - lastIdGenerated = generator.nextStringIdentifier(); - return lastIdGenerated; - } - } - - // Reset the value of the last identifier generated. - public void reset() { - lastIdGenerated = null; - }; - -} 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 new file mode 100644 index 000000000..d9e548449 --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDGenerator.java @@ -0,0 +1,61 @@ +/* + * StringIDGenerator + * + *

An identifier generator that stores and returns a static String.

+ * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +// @TODO: Need to set and enforce maximum String length. + +package org.collectionspace.services.id; + +public class StringIDGenerator implements IDGenerator { + + private String initialValue = null; + private String currentValue = null; + + public StringIDGenerator(String initialValue) throws IllegalArgumentException { + + if ( initialValue == null ) { + throw new IllegalArgumentException("Initial value must not be null"); + } + + if ( initialValue == "" ) { + throw new IllegalArgumentException("Initial value must not be empty"); + } + + this.initialValue = initialValue; + this.currentValue = initialValue; + + } + + public synchronized void reset() { + // Do nothing + } + + public synchronized String getInitialID() { + return this.initialValue; + } + + public synchronized String getCurrentID() { + return this.currentValue; + } + + public synchronized String getNextID() { + return this.currentValue; + } + +} diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDPart.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDPart.java new file mode 100644 index 000000000..16ae17420 --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/StringIDPart.java @@ -0,0 +1,29 @@ + /* + * StringIDGenerator + * + * Models a part of an identifier (ID) whose values are an alphabetic series. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +package org.collectionspace.services.id; + +public class StringIDPart extends IDPart { + + public StringIDPart(String baseVal) { + super(new StringIDGenerator(baseVal)); + }; + +} 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 new file mode 100644 index 000000000..0e09bf4ec --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDGenerator.java @@ -0,0 +1,82 @@ +/* + * YearIDGenerator + * + *

An identifier generator that stores and returns the current year + * as a String object.

+ * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +// @TODO: Need to understand and reflect time zone issues. + +package org.collectionspace.services.id; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +public class YearIDGenerator implements IDGenerator { + + private String initialValue = null; + private String currentValue = null; + + public YearIDGenerator() throws IllegalArgumentException { + + String currentYear = getCurrentYear(); + this.initialValue = currentYear; + this.currentValue = currentYear; + + } + + public YearIDGenerator(String initialValue) throws IllegalArgumentException { + + if ( initialValue == null ) { + throw new IllegalArgumentException("Initial value must not be null"); + } + + if ( initialValue == "" ) { + throw new IllegalArgumentException("Initial value must not be empty"); + } + + // @TODO: Add regex-based validation here, by calling a + // to-be-added validate() method + + this.initialValue = initialValue; + this.currentValue = initialValue; + + } + + public synchronized void reset() { + this.currentValue = this.initialValue; + } + + public synchronized String getInitialID() { + return this.initialValue; + } + + public synchronized String getCurrentID() { + return this.currentValue; + } + + public synchronized String getNextID() { + return this.currentValue; + } + + public String getCurrentYear() { + Calendar cal = GregorianCalendar.getInstance(); + int y = cal.get(Calendar.YEAR); + return Integer.toString(y); + } + +} diff --git a/sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDPart.java b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDPart.java new file mode 100644 index 000000000..f0d7ede9d --- /dev/null +++ b/sandbox/aron/id/src/main/java/org/collectionspace/services/id/YearIDPart.java @@ -0,0 +1,34 @@ + /* + * YearIDGenerator + * + * Models a part of an identifier (ID) whose value is the current year + * or a supplied year. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +// @TODO: Add Javadoc comments + +package org.collectionspace.services.id; + +public class YearIDPart extends IDPart { + + public YearIDPart() { + super(new YearIDGenerator()); + }; + + public YearIDPart(String baseVal) { + super(new YearIDGenerator(baseVal)); + }; + +} diff --git a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticGeneratorTest.java b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticGeneratorTest.java deleted file mode 100644 index 34d297fb1..000000000 --- a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticGeneratorTest.java +++ /dev/null @@ -1,198 +0,0 @@ - /* - * AlphabeticGeneratorTest - * - * Test class for AlphabeticGenerator. - * - * Copyright 2009 Regents of the University of California - * - * Licensed under the Educational Community License (ECL), Version 2.0. - * You may not use this file except in compliance with this License. - * - * You may obtain a copy of the ECL 2.0 License at - * https://source.collectionspace.org/collection-space/LICENSE.txt - * - * @author $Author$ - * @version $Revision$ - * $Date$ - */ - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// See -// for Exception handling in JUnit. - -package org.collectionspace.services.id; - -import org.apache.commons.id.StringIdentifierGenerator; -import static org.junit.Assert.fail; -import junit.framework.TestCase; - -public class AlphabeticGeneratorTest extends TestCase { - - final boolean NO_WRAP = false; - final boolean WRAP = true; - StringIdentifierGenerator generator; - - public void testNoWrapAndInitialValue() { - - // @TODO: Split this test into more focused individual tests - - // All lowercase initial values - - generator = new AlphabeticGenerator(NO_WRAP, "a"); - assertEquals("b", generator.nextStringIdentifier()); - assertEquals("c", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(NO_WRAP, "x"); - assertEquals("y", generator.nextStringIdentifier()); - assertEquals("z", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(NO_WRAP, "aa"); - assertEquals("ab", generator.nextStringIdentifier()); - assertEquals("ac", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(NO_WRAP, "ay"); - assertEquals("az", generator.nextStringIdentifier()); - assertEquals("ba", generator.nextStringIdentifier()); - assertEquals("bb", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(NO_WRAP, "zx"); - assertEquals("zy", generator.nextStringIdentifier()); - assertEquals("zz", generator.nextStringIdentifier()); - - // All uppercase initial values - - generator = new AlphabeticGenerator(NO_WRAP, "A"); - assertEquals("B", generator.nextStringIdentifier()); - assertEquals("C", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(NO_WRAP, "X"); - assertEquals("Y", generator.nextStringIdentifier()); - assertEquals("Z", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(NO_WRAP, "AA"); - assertEquals("AB", generator.nextStringIdentifier()); - assertEquals("AC", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(NO_WRAP, "AY"); - assertEquals("AZ", generator.nextStringIdentifier()); - assertEquals("BA", generator.nextStringIdentifier()); - assertEquals("BB", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(NO_WRAP, "ZX"); - assertEquals("ZY", generator.nextStringIdentifier()); - assertEquals("ZZ", generator.nextStringIdentifier()); - - } - - public void testWrapAndInitialLowercaseValue() { - - generator = new AlphabeticGenerator(WRAP, "x"); - assertEquals("y", generator.nextStringIdentifier()); - assertEquals("z", generator.nextStringIdentifier()); - assertEquals("a", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(WRAP, "zx"); - assertEquals("zy", generator.nextStringIdentifier()); - assertEquals("zz", generator.nextStringIdentifier()); - assertEquals("aa", generator.nextStringIdentifier()); - - } - - public void testOverflowWithNoWrapAndInitialLowercaseValue() - throws Exception { - - try { - generator = new AlphabeticGenerator(NO_WRAP, "zx"); - assertEquals("zy", generator.nextStringIdentifier()); - assertEquals("zz", generator.nextStringIdentifier()); - // Should throw IllegalStateException - assertNotNull(generator.nextStringIdentifier()); - fail("Should have thrown IllegalStateException here"); - } catch (IllegalStateException expected) { - // This Exception should be thrown, and thus the test should pass. - } - - } - - public void testWrapAndInitialUppercaseValue() { - - generator = new AlphabeticGenerator(WRAP, "X"); - assertEquals("Y", generator.nextStringIdentifier()); - assertEquals("Z", generator.nextStringIdentifier()); - assertEquals("A", generator.nextStringIdentifier()); - - generator = new AlphabeticGenerator(WRAP, "ZX"); - assertEquals("ZY", generator.nextStringIdentifier()); - assertEquals("ZZ", generator.nextStringIdentifier()); - assertEquals("AA", generator.nextStringIdentifier()); - - } - - public void testOverflowWithNoWrapAndInitialUppercaseValue() { - - try { - generator = new AlphabeticGenerator(NO_WRAP, "ZX"); - assertEquals("ZY", generator.nextStringIdentifier()); - assertEquals("ZZ", generator.nextStringIdentifier()); - // Should throw IllegalStateException - assertNotNull(generator.nextStringIdentifier()); - fail("Should have thrown IllegalStateException here"); - } catch (IllegalStateException expected) { - // This Exception should be thrown, and thus the test should pass. - } - - } - - public void testNonAlphabeticInitialValue() { - try { - generator = new AlphabeticGenerator(NO_WRAP, "&*432"); - fail("Should have thrown IllegalArgumentException here"); - } catch (IllegalArgumentException expected) { - // This Exception should be thrown, and thus the test should pass. - } - } - - public void testNullInitialValue() { - try { - generator = new AlphabeticGenerator(NO_WRAP, null); - fail("Should have thrown IllegalArgumentException here"); - } catch (IllegalArgumentException expected) { - // This Exception should be thrown, and thus the test should pass. - } - } - - public void testEmptyStringInitialValue() { - try { - generator = new AlphabeticGenerator(NO_WRAP, ""); - fail("Should have thrown IllegalArgumentException here"); - } catch (IllegalArgumentException expected) { - // This Exception should be thrown, and thus the test should pass. - } - } - - public void testAllSpaceCharsInitialValue() { - try { - generator = new AlphabeticGenerator(NO_WRAP, " "); - fail("Should have thrown IllegalArgumentException here"); - } catch (IllegalArgumentException expected) { - // This Exception should be thrown, and thus the test should pass. - } - } - -} 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 new file mode 100644 index 000000000..7599f9f29 --- /dev/null +++ b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticIDPartTest.java @@ -0,0 +1,202 @@ +/* + * AlphabeticIDPartTest + * + * Test class for AlphabeticIDPart. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +package org.collectionspace.services.id; + +import static org.junit.Assert.fail; +import junit.framework.TestCase; + +public class AlphabeticIDPartTest extends TestCase { + + IDPart part; + + public void testGetNextIDLowercase() { + + part = new AlphabeticIDPart("a"); + assertEquals("b", part.getNextID()); + assertEquals("c", part.getNextID()); + + part = new AlphabeticIDPart("x"); + assertEquals("y", part.getNextID()); + assertEquals("z", part.getNextID()); + + 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 testGetNextIDUppercase() { + + part = new AlphabeticIDPart("A"); + assertEquals("B", part.getNextID()); + assertEquals("C", part.getNextID()); + + part = new AlphabeticIDPart("X"); + assertEquals("Y", part.getNextID()); + assertEquals("Z", part.getNextID()); + + 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 testResetLowercase() { + + part = new AlphabeticIDPart("zx"); + assertEquals("zy", part.getNextID()); + assertEquals("zz", part.getNextID()); + part.reset(); + assertEquals("zx", part.getCurrentID()); + + } + + public void testResetUppercase() { + + part = new AlphabeticIDPart("RA"); + assertEquals("RB", part.getNextID()); + assertEquals("RC", part.getNextID()); + part.reset(); + assertEquals("RB", part.getNextID()); + + } + + public void testInitialLowercase() { + + part = new AlphabeticIDPart("aaa"); + assertEquals("aaa", part.getInitialID()); + + } + + public void testInitialUppercase() { + + part = new AlphabeticIDPart("AZ"); + assertEquals("AZ", part.getInitialID()); + + } + + public void testCurrentLowercase() { + + part = new AlphabeticIDPart("aaa"); + assertEquals("aaa", part.getCurrentID()); + assertEquals("aab", part.getNextID()); + assertEquals("aac", part.getNextID()); + assertEquals("aac", part.getCurrentID()); + assertEquals("aad", part.getNextID()); + + } + + public void testCurrentUppercase() { + + part = new AlphabeticIDPart("A"); + assertEquals("A", part.getCurrentID()); + assertEquals("B", part.getNextID()); + assertEquals("C", part.getNextID()); + assertEquals("C", part.getCurrentID()); + assertEquals("D", part.getNextID()); + + } + + public void testOverflowLowercase() { + + try { + part = new AlphabeticIDPart("zx"); + assertEquals("zy", part.getNextID()); + assertEquals("zz", part.getNextID()); + // Should throw IllegalStateException + assertNotNull(part.getNextID()); + fail("Should have thrown IllegalStateException here"); + } catch (IllegalStateException expected) { + // This Exception should be thrown, and thus the test should pass. + } + + } + + public void testOverflowUppercase() { + + try { + part = new AlphabeticIDPart("X"); + assertEquals("Y", part.getNextID()); + assertEquals("Z", part.getNextID()); + // Should throw IllegalStateException + assertNotNull(part.getNextID()); + fail("Should have thrown IllegalStateException here"); + } catch (IllegalStateException expected) { + // This Exception should be thrown, and thus the test should pass. + } + + } + + public void testNonAlphabeticInitialValue() { + try { + part = new AlphabeticIDPart("&*432"); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + } + + public void testNullInitialValue() { + try { + part = new AlphabeticIDPart(null); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + } + + public void testEmptyStringInitialValue() { + try { + part = new AlphabeticIDPart(""); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + } + + public void testAllSpaceCharsInitialValue() { + try { + part = new AlphabeticIDPart(" "); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + } + + // @TODO: Add more tests of boundary conditions, exceptions ... + +} diff --git a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticSeriesIDPartTest.java b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticSeriesIDPartTest.java deleted file mode 100644 index 4237ebad5..000000000 --- a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/AlphabeticSeriesIDPartTest.java +++ /dev/null @@ -1,44 +0,0 @@ - /* - * AlphabeticSeriesIDPartTest - * - * Test class for AlphabeticSeriesIDPart. - * - * Copyright 2009 Regents of the University of California - * - * Licensed under the Educational Community License (ECL), Version 2.0. - * You may not use this file except in compliance with this License. - * - * You may obtain a copy of the ECL 2.0 License at - * https://source.collectionspace.org/collection-space/LICENSE.txt - * - * @author $Author$ - * @version $Revision$ - * $Date$ - */ - -package org.collectionspace.services.id; - -import junit.framework.TestCase; - -public class AlphabeticSeriesIDPartTest extends TestCase { - - AlphabeticSeriesIDPart part; - - public void testInitialValue() { - - part = new AlphabeticSeriesIDPart("a"); - assertEquals("a", part.nextIdentifier()); - assertEquals("b", part.nextIdentifier()); - - part = new AlphabeticSeriesIDPart("x"); - assertEquals("x", part.nextIdentifier()); - assertEquals("y", part.nextIdentifier()); - assertEquals("z", part.nextIdentifier()); - part.reset(); - assertEquals("x", part.nextIdentifier()); - - } - - // Add 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 new file mode 100644 index 000000000..07a352c5e --- /dev/null +++ b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/IDPatternTest.java @@ -0,0 +1,62 @@ +/* + * IDPatternTest + * + * Test class for IDPattern. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +package org.collectionspace.services.id; + +import static org.junit.Assert.fail; +import java.util.Vector; +import junit.framework.TestCase; + +public class IDPatternTest extends TestCase { + + IDPattern pattern; + IDPart part; + + public void testCurrentID() { + + Vector parts = new Vector(); + parts.add(new YearIDPart("2009")); + parts.add(new StringIDPart(".")); + parts.add(new NumericIDPart("1")); + pattern = new IDPattern(parts); + + assertEquals("2009.1", pattern.getCurrentID()); + + } + + public void testAddCurrentID() { + + pattern = new IDPattern(); + pattern.add(new YearIDPart("2009")); + pattern.add(new StringIDPart(".")); + pattern.add(new NumericIDPart("1")); + + assertEquals("2009.1", pattern.getCurrentID()); + + } + + public void testEmptyPartsListCurrentID() { + + pattern = new IDPattern(); + assertEquals("", pattern.getCurrentID()); + + } + + // @TODO: Add more tests of boundary conditions, exceptions ... + +} diff --git a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/NumericIDPartTest.java b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/NumericIDPartTest.java new file mode 100644 index 000000000..51a5ba61f --- /dev/null +++ b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/NumericIDPartTest.java @@ -0,0 +1,101 @@ +/* + * NumericIDPartTest + * + * Test class for NumericIDPart. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +package org.collectionspace.services.id; + +import static org.junit.Assert.fail; +import junit.framework.TestCase; + +public class NumericIDPartTest extends TestCase { + + IDPart part; + + public void testNextID() { + + part = new NumericIDPart("0"); + assertEquals("1", part.getNextID()); + assertEquals("2", part.getNextID()); + assertEquals("3", part.getNextID()); + + part = new NumericIDPart("25"); + assertEquals("26", part.getNextID()); + assertEquals("27", part.getNextID()); + assertEquals("28", part.getNextID()); + + } + + public void testReset() { + + part = new NumericIDPart("25"); + assertEquals("26", part.getNextID()); + assertEquals("27", part.getNextID()); + assertEquals("28", part.getNextID()); + part.reset(); + assertEquals("26", part.getNextID()); + + } + + public void testInitialID() { + + part = new NumericIDPart("0"); + assertEquals("0", part.getInitialID()); + + part = new NumericIDPart("25"); + assertEquals("25", part.getInitialID()); + + } + + public void testCurrentID() { + + part = new NumericIDPart("0"); + assertEquals("0", part.getCurrentID()); + assertEquals("1", part.getNextID()); + assertEquals("2", part.getNextID()); + assertEquals("2", part.getCurrentID()); + assertEquals("3", part.getNextID()); + + part = new NumericIDPart("25"); + assertEquals("25", part.getCurrentID()); + + } + + public void testNullInitialValue() { + + try { + part = new NumericIDPart(null); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + + } + + public void testNonLongParseableInitialValue() { + + try { + part = new NumericIDPart("not a long parseable value"); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + + } + + // @TODO: Add more tests of boundary conditions, exceptions ... + +} diff --git a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/StringIDPartTest.java b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/StringIDPartTest.java new file mode 100644 index 000000000..4c0af39a0 --- /dev/null +++ b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/StringIDPartTest.java @@ -0,0 +1,76 @@ +/* + * StringIDPartTest + * + * Test class for StringIDPart. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +package org.collectionspace.services.id; + +import static org.junit.Assert.fail; +import junit.framework.TestCase; + +public class StringIDPartTest extends TestCase { + + IDPart part; + + public void testNextID() { + part = new StringIDPart("XYZ"); + assertEquals("XYZ", part.getNextID()); + } + + public void testReset() { + + part = new StringIDPart("."); + assertEquals(".", part.getNextID()); + part.reset(); + assertEquals(".", part.getNextID()); + + } + + public void testInitialID() { + part = new StringIDPart("-"); + assertEquals("-", part.getInitialID()); + } + + public void testCurrentID() { + part = new StringIDPart("- -"); + assertEquals("- -", part.getCurrentID()); + } + + public void testNullInitialValue() { + + try { + part = new StringIDPart(null); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + + } + + public void testEmptyInitialValue() { + + try { + part = new StringIDPart(""); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + + } + + // @TODO: Add more tests of boundary conditions, exceptions ... + +} diff --git a/sandbox/aron/id/src/test/java/org/collectionspace/services/id/YearIDPartTest.java b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/YearIDPartTest.java new file mode 100644 index 000000000..132eae4e9 --- /dev/null +++ b/sandbox/aron/id/src/test/java/org/collectionspace/services/id/YearIDPartTest.java @@ -0,0 +1,94 @@ +/* + * YearIDPartTest + * + * Test class for YearIDPart. + * + * Copyright 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * @author $Author: aron $ + * @version $Revision: 267 $ + * $Date: 2009-06-19 19:03:38 -0700 (Fri, 19 Jun 2009) $ + */ + +package org.collectionspace.services.id; + +import static org.junit.Assert.fail; +import java.util.Calendar; +import java.util.GregorianCalendar; +import junit.framework.TestCase; + +public class YearIDPartTest extends TestCase { + + IDPart part; + String year = "1999"; + + public String getCurrentYear() { + Calendar cal = GregorianCalendar.getInstance(); + int y = cal.get(Calendar.YEAR); + return Integer.toString(y); + } + + public void testCurrentID() { + + part = new YearIDPart(); + assertEquals(getCurrentYear(), part.getCurrentID()); + + part = new YearIDPart(year); + assertEquals(year, part.getCurrentID()); + + } + + +/* + public void testNextID() { + part = new YearIDPart("XYZ"); + assertEquals("XYZ", part.getNextID()); + } + + public void testReset() { + + part = new YearIDPart("."); + assertEquals(".", part.getNextID()); + part.reset(); + assertEquals(".", part.getNextID()); + + } + + public void testInitialID() { + part = new YearIDPart("-"); + assertEquals("-", part.getInitialID()); + } + +*/ + + public void testNullInitialValue() { + + try { + part = new YearIDPart(null); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + + } + + public void testEmptyInitialValue() { + + try { + part = new YearIDPart(""); + fail("Should have thrown IllegalArgumentException here"); + } catch (IllegalArgumentException expected) { + // This Exception should be thrown, and thus the test should pass. + } + + } + + // @TODO: Add more tests of boundary conditions, exceptions ... + +} diff --git a/sandbox/aron/id/third-party/commons-id-1.0-SNAPSHOT.jar b/sandbox/aron/id/third-party/commons-id-1.0-SNAPSHOT.jar deleted file mode 100644 index f60f41a069d9c499ce8be0232a846102814f6e0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55341 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[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