Replace enums with charsequence sub classes

This commit is contained in:
Paul Schaub 2018-11-15 17:54:34 +01:00
parent cb7e0e4c35
commit ddd0b27a5c
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
7 changed files with 109 additions and 129 deletions

View file

@ -0,0 +1,63 @@
package de.vanitasvitae.imi.codes;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
public abstract class BoundedCharSequence implements CharSequence {
private final Pattern regex;
private final String value;
protected BoundedCharSequence(Pattern regex, String value) throws InvalidOptionException {
this.regex = regex;
if (!matchPattern(value)) {
throw new InvalidOptionException("Invalid input.");
}
this.value = value;
}
private boolean matchPattern(String input) {
return regex.matcher(input).matches();
}
@Override
public int length() {
return value.length();
}
@Override
public char charAt(int i) {
return value.charAt(i);
}
@Override
public CharSequence subSequence(int a, int b) {
return value.subSequence(a, b);
}
@Override
public IntStream chars() {
return value.chars();
}
@Override
public IntStream codePoints() {
return value.codePoints();
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object o) {
BoundedCharSequence b = (BoundedCharSequence) o;
return toString().equals(b.toString());
}
@Override
public String toString() {
return value;
}
}

View file

@ -9,16 +9,6 @@ public class InputValidator {
public static final String REGEX_ALPHABET = "[a-zA-Z0-9]"; public static final String REGEX_ALPHABET = "[a-zA-Z0-9]";
public static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; public static final String ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
/*
REGEX pattern for study numbers. Allowed are all 3-letter numbers from the alphabet [a-zA-Z0-9].
*/
private static final Pattern STUDY_NUMBER_MATCHER = Pattern.compile(REGEX_ALPHABET +"{3}");
/*
REGEX pattern for sample numbers. Allowed are four-letter numbers from the alphabet [a-zA-Z0-9].
*/
private static final Pattern SAMPLE_NUMBER_MATCHER = Pattern.compile(REGEX_ALPHABET +"{4}");
/** /**
* Validate a given study number. Valid study numbers are 3-letter codes from the alphabet [a-zA-Z0-9]. * Validate a given study number. Valid study numbers are 3-letter codes from the alphabet [a-zA-Z0-9].
* *
@ -27,12 +17,8 @@ public class InputValidator {
* *
* @throws InvalidOptionException if the input String doesn't match the regex. * @throws InvalidOptionException if the input String doesn't match the regex.
*/ */
public static String validateStudyNumber(String in) throws InvalidOptionException { public static StudyNumber validateStudyNumber(String in) throws InvalidOptionException {
if (!STUDY_NUMBER_MATCHER.matcher(in).matches()) { return new StudyNumber(in);
throw new InvalidOptionException("Study number must be a three digit number from the alphabet [a-zA-Z0-9].");
}
return in;
} }
/** /**
@ -45,12 +31,7 @@ public class InputValidator {
* @throws NullPointerException in case the given String is {@code null}. * @throws NullPointerException in case the given String is {@code null}.
*/ */
public static SampleType validateSampleType(String in) throws InvalidOptionException { public static SampleType validateSampleType(String in) throws InvalidOptionException {
try { return new SampleType(in);
return SampleType.getEnum(in);
} catch (IllegalArgumentException e) {
String message = "Invalid sample type \"" + in + "\".";
throw new InvalidOptionException(message, e);
}
} }
/** /**

View file

@ -35,7 +35,7 @@ public class Main {
} }
// Parse arguments // Parse arguments
String studyNumber; StudyNumber studyNumber;
SampleType sampleType; SampleType sampleType;
int numberOfCodes; int numberOfCodes;
File outputPath; File outputPath;
@ -58,7 +58,7 @@ public class Main {
+ " output: " + outputPath + " browser: " + externalBrowser); + " output: " + outputPath + " browser: " + externalBrowser);
for (int i = 0; i < numberOfCodes; i++) { for (int i = 0; i < numberOfCodes; i++) {
System.out.format("%s%04d%n", studyNumber + sampleType, i); System.out.format("%s%04d%n", studyNumber.toString() + sampleType, i);
} }
} }

View file

@ -1,62 +1,22 @@
package de.vanitasvitae.imi.codes; package de.vanitasvitae.imi.codes;
public enum SampleType { import java.util.regex.Pattern;
// Hard coded values. Prepended with an underscore to allow numeric values (eg. _2).
_b('b', "Blood"),
_u('u', "Urine"),
;
// Members of the enum
private final char name;
private final String description;
/** /**
* Create a SampleType enum which is described by a name (one letter from the alphabet [a-zA-Z0-9]) and a * Sub-class of {@link CharSequence}, which can only represent char sequences of length 1 from the alphabet [a-zA-Z0-9].
* human readable description.
*
* @param name one letter name
* @param description description
*/ */
SampleType(char name, String description) { public class SampleType extends BoundedCharSequence {
this.name = name;
this.description = description; private static final Pattern REGEX = Pattern.compile(InputValidator.REGEX_ALPHABET);
}
/** /**
* Override {@link Enum#toString()} in order to return the name of the enum instead of its value. * Create a new {@link SampleType} sequence. The constructor checks, whether the input String is of length 1 and from
* the alphabet [a-zA-Z9-0].
* *
* @return name * @param value input string
* @throws InvalidOptionException if the input doesn't match the regex.
*/ */
@Override public SampleType(String value) throws InvalidOptionException {
public String toString() { super(REGEX, value);
return "" + name;
}
/**
* Return the human readable description of the enum.
*/
public String getDescription() {
return description;
}
/**
* Replacement method for {@link #valueOf(String)}. You MUST use this method instead, as we have to escape
* the names of the enums with a underscore in order to allow for numerals to be used as enum name (for example "_0").
* This method takes a string value (eg. "u") and returns the corresponding enum ("_u").
*
* @param name name of the enum without an underscore.
* @return enum corresponding to the name.
*
* @throws IllegalArgumentException if no matching enum was found.
*/
public static SampleType getEnum(String name) {
for (SampleType s : SampleType.values()) {
if (s.toString().equals(name)) {
return s;
}
}
throw new IllegalArgumentException("No SampleType found for name \"" + name + "\".");
} }
} }

View file

@ -0,0 +1,22 @@
package de.vanitasvitae.imi.codes;
import java.util.regex.Pattern;
/**
* Sub-class of {@link CharSequence}, which can only represent char sequences of length 3 from the alphabet [a-zA-Z0-9].
*/
public class StudyNumber extends BoundedCharSequence {
private static final Pattern REGEX = Pattern.compile(InputValidator.REGEX_ALPHABET + "{3}");
/**
* Create a new {@link StudyNumber} sequence. The constructor checks, whether the input String is of length 3 and from
* the alphabet [a-zA-Z9-0].
*
* @param value input string
* @throws InvalidOptionException if the input doesn't match the regex
*/
protected StudyNumber(String value) throws InvalidOptionException {
super(REGEX, value);
}
}

View file

@ -2,6 +2,8 @@ package de.vanitasvitae.imi.codes;
import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.Random; import java.util.Random;
import org.junit.Test; import org.junit.Test;
@ -19,7 +21,7 @@ public class InputValidatorTest {
public void testValidateStudyNumber() throws InvalidOptionException { public void testValidateStudyNumber() throws InvalidOptionException {
String in = generateTestStringOfLength(3); String in = generateTestStringOfLength(3);
assertEquals("validateStudyNumber must accept valid three digit inputs from the alphabet [a-zA-Z0-9].", assertEquals("validateStudyNumber must accept valid three digit inputs from the alphabet [a-zA-Z0-9].",
in, InputValidator.validateStudyNumber(in)); new StudyNumber(in), InputValidator.validateStudyNumber(in));
} }
/** /**
@ -84,24 +86,15 @@ public class InputValidatorTest {
*/ */
@Test @Test
public void testValidateSampleType() throws InvalidOptionException { public void testValidateSampleType() throws InvalidOptionException {
for (SampleType t : SampleType.values()) { List<SampleType> sampleSampleTypes = new ArrayList<>();
sampleSampleTypes.add(new SampleType("u"));
sampleSampleTypes.add(new SampleType("b"));
for (SampleType t : sampleSampleTypes) {
assertEquals("validateSampleType didn't return expected value for input \"" + t.toString() + "\"", assertEquals("validateSampleType didn't return expected value for input \"" + t.toString() + "\"",
t, InputValidator.validateSampleType(t.toString())); t, InputValidator.validateSampleType(t.toString()));
} }
} }
/**
* Test, whether {@link InputValidator#validateSampleType(String)} throws a {@link InvalidOptionException} for
* unknown sample types (in this case {@code x}).
*
* @throws InvalidOptionException expected
*/
@Test(expected = InvalidOptionException.class)
public void testValidateSampleTypeFailsForUnknownInputs() throws InvalidOptionException {
// -> InvalidOptionException
InputValidator.validateSampleType("x");
}
/** /**
* Test, whether {@link InputValidator#validateNumberOfCodes(String)} returns the expected {@link Integer}. * Test, whether {@link InputValidator#validateNumberOfCodes(String)} returns the expected {@link Integer}.
* In this method, posit * In this method, posit

View file

@ -1,39 +0,0 @@
package de.vanitasvitae.imi.codes;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import java.util.regex.Pattern;
import org.junit.Test;
/**
* Test functionality of the {@link SampleType} enum.
*/
public class SampleTypeTest {
private static final Pattern SAMPLE_TYPE_VALUE_PATTERN = Pattern.compile(InputValidator.REGEX_ALPHABET);
/**
* Test, whether all {@link SampleType} names are of length 1 and from the alphabet [a-zA-Z0-9].
*/
@Test
public void testSampleTypeNames() {
for (SampleType t : SampleType.values()) {
assertTrue("SampleType value names must be one letter from the alphabet [a-zA-Z0-9].",
SAMPLE_TYPE_VALUE_PATTERN.matcher(t.toString()).matches());
}
}
/**
* Test, whether {@link SampleType#getEnum(String)} correctly maps {@link SampleType#name} to the corresponding
* values.
*/
@Test
public void testGetEnum() {
for (SampleType t : SampleType.values()) {
assertEquals("getEnum must return the SampleType, which corresponds to the given name.",
t, SampleType.getEnum(t.toString()));
}
}
}