package org.collectionspace.services.id.part;
-// @TODO Largely unimplemented at present.
-// Corresponding test class has not yet been created.
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+
+// @TODO Largely unimplemented at present. Much code is non-working.
public class AlphabeticSequenceIDPart extends SequenceIDPart {
+ private IDPartOutputFormatter formatter = new NoOpIDPartOutputFormatter();
+ private IDPartValidator validator = new NoOpIDPartValidator();
+
// @TODO Externalize character sequences to their own class.
private AlphabeticSequenceIDPart.AlphabeticCharSequence charsInSequence;
- private IDPartOutputFormatter formatter;
- private IDPartValidator validator;
- private char initialValue;
+
+ LinkedHashSet<Character> alphabeticSequence = new LinkedHashSet<Character>();
+ private ArrayList<Character> initialValue = new ArrayList<Character>();
+ private ArrayList<Character> currentValue = new ArrayList<Character>();
+
+ private static final char NULL_CHAR = '\u0000';
+
+ private char startChar = NULL_CHAR;
+ private char endChar = NULL_CHAR;
public AlphabeticSequenceIDPart () {
}
+ public AlphabeticSequenceIDPart(LinkedHashSet<Character> sequence) {
+ this.alphabeticSequence = sequence;
+ Character[] chars = (Character[]) alphabeticSequence.toArray();
+ this.startChar = chars[0].charValue();
+ // initialValue.add(new Character(start.char));
+ this.endChar = chars[chars.length - 1].charValue();
+ }
+
@Override
public IDPartOutputFormatter getOutputFormatter () {
return formatter;
@Override
public String nextID() {
- throw new UnsupportedOperationException("Not supported yet.");
+
+ // Get next values for each character, from right to left
+ // (least significant to most significant).
+ boolean expandIdentifier = false;
+ int size = this.currentValue.size();
+ char ch;
+ for (int i = (size - 1); i >= 0; i--) {
+
+ ch = this.currentValue.get(i).charValue();
+
+ // 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 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,
+ // as well as a mechanism for setting the maximum expansion
+ // permitted.
+ 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 {
+ ch++;
+ this.currentValue.set(i, Character.valueOf(ch));
+ i = -1;
+ break;
+ }
+
+ }
+
+ // If we are expanding the size of the identifier, insert a new
+ // value at the most significant (leftmost) character position,
+ // sliding other values to the right.
+ if (expandIdentifier) {
+ this.currentValue.add(0, Character.valueOf(this.startChar));
+ }
+
+ return toIDString(this.currentValue);
+
}
public char getInitialValue () {
- return initialValue;
+ throw new UnsupportedOperationException("Not supported yet.");
}
public void setInitialValue (char val) {
- this.initialValue = val;
+ throw new UnsupportedOperationException("Not supported yet.");
}
public AlphabeticSequenceIDPart.AlphabeticCharSequence getCharsInSequence () {
;
}
+ public String toIDString(ArrayList<Character> characters) {
+ StringBuffer sb = new StringBuffer();
+ for ( Character ch : characters ) {
+ sb.append(ch.toString());
+ }
+ return sb.toString();
+ }
}
// other invocation of this constructor."
private static Random r = new Random();
- public final static int DEFAULT_MAX_VALUE = Integer.MAX_VALUE - 1;
+ public final static int DEFAULT_MAX_VALUE = Integer.MAX_VALUE - 2;
public final static int DEFAULT_MIN_VALUE = 0;
private int maxValue = DEFAULT_MAX_VALUE;
}
private void setMaxValue(int maxVal) {
- if (0 < maxVal && maxVal < DEFAULT_MAX_VALUE) {
+ if (0 < maxVal && maxVal <= DEFAULT_MAX_VALUE) {
this.maxValue = maxVal;
} else {
String msg =
- "Invalid maximum value for random number. " +
+ "Invalid maximum value '" +
+ Integer.toString(maxVal) +
+ "' for random number. " +
"Must be between 1 and " +
- Integer.toString(DEFAULT_MAX_VALUE - 1) + ".";
- logger.error(msg);
+ Integer.toString(DEFAULT_MAX_VALUE) +
+ ", inclusive.";
+ logger.info(msg);
throw new IllegalArgumentException(msg);
}
}
this.minValue = minVal;
} else {
String msg =
- "Invalid minimum value for random number. " +
+ "Invalid minimum value '" +
+ Integer.toString(minVal) +
+ "' for random number. " +
"Must be between 0 and " +
- Integer.toString(this.maxValue - 1) + ".";
- logger.error(msg);
+ Integer.toString(this.maxValue - 1) +
+ ", inclusive (i.e. less than the supplied maximum value " +
+ "of " + Integer.toString(this.maxValue) + ").";
+ logger.info(msg);
throw new IllegalArgumentException(msg);
}
}
@Override
public String generateID(){
// Returns an evenly distributed random value between 0
- // and the maximum value.
+ // and the maximum value. An even distribution decreases
+ // randomness but is more likely to meet end user requirements
+ // and expectations.
+ //
// See http://mindprod.com/jgloss/pseudorandom.html
//
// Note: Random.nextInt() returns a pseudorandom number
// between 0 and n-1 inclusive, not a number between 0 and n.
+
+ // @TODO Consider adding code to ensure the uniqueness of
+ // each generated pseudorandom number, until all possible
+ // values within the inclusive set have been generated.
return
Integer.toString(r.nextInt(
this.maxValue - this.minValue + 1) + this.minValue);
import org.collectionspace.services.id.part.GregorianDateIDPart;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
import org.testng.Assert;
import org.testng.annotations.Test;
GregorianDateIDPart part;
+ final static String MONTH_FULL_NAME_PATTERN = "MMMM";
+ final static String FRENCH_LANGUAGE_ISO_CODE = "fr";
+ final static Locale FRENCH_LANGUAGE_LOCALE =
+ new Locale(FRENCH_LANGUAGE_ISO_CODE, "");
+
@Test
public void newID() {
-
- // @TODO Replace these hard-coded expedients, which will all fail
- // when the current month or year doesn't match these asserted values.
part = new GregorianDateIDPart("yyyy");
- Assert.assertEquals(part.newID(), "2009");
+ Assert.assertEquals(part.newID(), currentYearAsString());
part = new GregorianDateIDPart("M");
- Assert.assertEquals(part.newID(), "11");
+ Assert.assertEquals(part.newID(), currentMonthNumberAsString());
- part = new GregorianDateIDPart("MMMM");
- Assert.assertEquals(part.newID(), "November");
+ part = new GregorianDateIDPart(MONTH_FULL_NAME_PATTERN);
+ Assert.assertEquals(part.newID(), currentMonthFullName());
- part = new GregorianDateIDPart("MMMM", "fr");
- // Month names are not capitalized in French.
- Assert.assertEquals(part.newID(), "novembre");
+ part = new GregorianDateIDPart(MONTH_FULL_NAME_PATTERN,
+ FRENCH_LANGUAGE_ISO_CODE);
+ Assert.assertEquals(part.newID(),
+ currentMonthFullNameLocalized(FRENCH_LANGUAGE_LOCALE));
}
Assert.assertTrue(part.getValidator().isValid(part.newID()));
}
+ public String currentYearAsString() {
+ int y = GregorianCalendar.getInstance().get(Calendar.YEAR);
+ return Integer.toString(y);
+ }
+
+ public String currentMonthNumberAsString() {
+ // Calendar.MONTH numbers begin with 0; hence the need to add 1.
+ int m = GregorianCalendar.getInstance().get(Calendar.MONTH) + 1;
+ return Integer.toString(m);
+ }
+
+ public String currentMonthFullName() {
+ SimpleDateFormat df =
+ new SimpleDateFormat(MONTH_FULL_NAME_PATTERN,
+ Locale.getDefault());
+ return df.format(GregorianCalendar.getInstance().getTime());
+ }
+
+ public String currentMonthFullNameLocalized(Locale locale) {
+ SimpleDateFormat df =
+ new SimpleDateFormat(MONTH_FULL_NAME_PATTERN, locale);
+ return df.format(GregorianCalendar.getInstance().getTime());
+ }
}
}
}
- @Test(expectedExceptions = IllegalArgumentException.class)
- public void minValueTooLow() {
- int minValue = -1;
+ @Test
+ public void defaultMaxValue() {
+ part = new RandomNumberIDPart(
+ JavaRandomNumberIDPartAlgorithm.DEFAULT_MAX_VALUE);
+ part.newID();
+ }
+
+ @Test(dependsOnMethods = {"defaultMaxValue"})
+ public void defaultMinValue() {
part = new RandomNumberIDPart(
- JavaRandomNumberIDPartAlgorithm.DEFAULT_MAX_VALUE, minValue);
+ JavaRandomNumberIDPartAlgorithm.DEFAULT_MAX_VALUE,
+ JavaRandomNumberIDPartAlgorithm.DEFAULT_MIN_VALUE);
+ part.newID();
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void maxValueTooHigh() {
- part = new RandomNumberIDPart(Integer.MAX_VALUE);
+ int maxValue = Integer.MAX_VALUE; // Value too high
+ part = new RandomNumberIDPart(maxValue);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void minValueTooLow() {
+ int maxValue = 10;
+ int minValue = -1; // Value too low
+ part = new RandomNumberIDPart(maxValue, minValue);
}
@Test