--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.collectionspace.services</groupId>
+ <artifactId>id</artifactId>
+ <packaging>jar</packaging>
+ <version>0.1-SNAPSHOT</version>
+ <name>id</name>
+ <url>http://maven.apache.org</url>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-id</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+ /*
+ * AlphabeticGenerator
+ *
+ * <p>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.</p>
+ *
+ * <p>The <code>wrap</code> property determines whether or not the sequence wraps
+ * when it reaches the largest value that can be represented in <code>size</code>.
+ * If <code>wrap</code> is false and the the maximum representable
+ * value is exceeded, an IllegalStateException is thrown</p>
+ *
+ * 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 {
+
+ /**
+ * <code>serialVersionUID</code> 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.</p>
+ *
+ * @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 <code>true</code> 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);
+ }
+}
--- /dev/null
+ /*
+ * 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);
+ };
+
+}
--- /dev/null
+ /*
+ * 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.
+ *
+ * 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 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;
+
+ // Constructor
+ public IDPart(StringIdentifierGenerator idGenerator) {
+ setGenerator(idGenerator);
+ }
+
+ // Sets the identifier generator
+ protected void setGenerator(StringIdentifierGenerator idGenerator) {
+ if (idGenerator != null) {
+ generator = idGenerator;
+ }
+ }
+
+ // Gets the next identifier
+ public String nextIdentifier() {
+ // @TODO: Add Exception-handling here ...
+ return generator.nextStringIdentifier();
+ };
+
+ // public boolean validate() {};
+
+}
--- /dev/null
+ /*
+ * 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;
+ };
+
+}
--- /dev/null
+ /*
+ * 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 <http://radio.javaranch.com/lasse/2007/05/17/1179405760728.html>
+// 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.
+ }
+ }
+
+}
--- /dev/null
+ /*
+ * 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 ...
+
+}
--- /dev/null
+#! /bin/sh
+
+mvn install:install-file \
+ -Dfile=./commons-id-1.0-SNAPSHOT.jar \
+ -DgroupId=org.apache.commons \
+ -DartifactId=commons-id \
+ -Dversion=1.0-SNAPSHOT \
+ -Dpackaging=jar