MCPDUtil.java
/**
* Copyright 2014 Global Crop Diversity Trust
*
* 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.genesys.util;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
public class MCPDUtil {
static Pattern mcpdSplit = Pattern.compile("\\s*;\\s*");
/**
* @deprecated Use {@link #isMcpdDate(String)} instead; this pattern does not
* enforce calendar validity or a minimum year.
*/
@Deprecated
public static final Pattern MCPDDATE_PATTERN = Pattern.compile("^(?:(?:[1-9]\\d{3})|(?:----)|(?:0000))(?:--|(?:0[0-9])|(?:1[0-2]))(?:--|(?:0[0-9])|(?:[1-2][0-9])|(?:3[0-1]))$");
public static final Pattern WIEWSCODE_PATTERN = Pattern.compile("^\\p{Upper}{3}(\\d){3,4}$");
private static final Pattern YEAR_ONLY = Pattern.compile("^\\d{4}$");
private static final Pattern MONTH_PART = Pattern.compile("^(0[1-9]|1[0-2])$");
private static final Pattern DAY_PART = Pattern.compile("^(0[1-9]|[12]\\d|3[01])$");
private static final String MISSING = "--";
private static final String MISSING_ZERO = "00";
/**
* Convert a List to MCPD "array string" with semicolon as separator.
*
* @param integers
* @return
*/
public static String toMcpdArray(Collection<?> objects) {
if (objects == null || objects.size() == 0)
return null;
StringBuilder sb = new StringBuilder();
objects.stream().filter(i -> i != null).forEach(i -> {
if (sb.length() > 0)
sb.append(";");
sb.append(i);
});
return StringUtils.defaultIfBlank(sb.toString(), null);
}
/**
* Convert an MCPD "array" (e.g. "10;20") to a {@link List} of Integers,
* excluding blank and null elements.
*
* @throws NumberFormatException
* if the input string contains non-numeric elements.
*/
public static List<Integer> toIntegers(String ints) {
if (StringUtils.isBlank(ints))
return null;
return Arrays.stream(mcpdSplit.split(ints)).filter(str -> StringUtils.isNotBlank(str)).map(i -> Integer.parseInt(i)).collect(Collectors.toList());
}
/**
* Convert an MCPD "array string" (e.g. "NOR051;AUS033") to a {@link List},
* excluding blank and null elements.
*
* @param strings
* @return
*/
public static List<String> toList(String strings) {
if (StringUtils.isBlank(strings))
return null;
return Arrays.stream(mcpdSplit.split(strings)).filter(str -> StringUtils.isNotBlank(str)).collect(Collectors.toList());
}
/**
* Validates a canonical 8-character MCPD date, including calendar correctness.
*
* @param date the date string
* @return {@code true} only if the value is non-blank, exactly 8 characters,
* has a 4-digit year {@code >= 1600}, a valid month or month
* placeholder, and a valid day or day placeholder. Day is only
* allowed when month is provided.
*/
public static boolean isMcpdDate(String date) {
return parseValidMcpdDate(date) != null;
}
private static LocalDate parseValidMcpdDate(String date) {
if (StringUtils.isBlank(date) || date.length() != 8) {
return null;
}
String yearPart = date.substring(0, 4);
String monthPart = date.substring(4, 6);
String dayPart = date.substring(6, 8);
if (!YEAR_ONLY.matcher(yearPart).matches()) {
return null;
}
int year = Integer.parseInt(yearPart);
if (year < 1600) {
return null;
}
boolean monthMissing = MISSING.equals(monthPart) || MISSING_ZERO.equals(monthPart);
boolean dayMissing = MISSING.equals(dayPart) || MISSING_ZERO.equals(dayPart);
if (!monthMissing) {
if (!MONTH_PART.matcher(monthPart).matches()) {
return null;
}
}
if (!dayMissing) {
if (monthMissing) {
return null;
}
if (!DAY_PART.matcher(dayPart).matches()) {
return null;
}
}
if (!monthMissing && dayMissing) {
return LocalDate.of(year, Integer.parseInt(monthPart), 1);
}
if (monthMissing && dayMissing) {
return LocalDate.of(year, 1, 1);
}
if (!monthMissing && !dayMissing) {
try {
return LocalDate.of(year, Integer.parseInt(monthPart), Integer.parseInt(dayPart));
} catch (DateTimeException e) {
return null;
}
}
// no match
return null;
}
/**
* Returns true when the value has the 8-character digit-or-dash shape used by
* canonical MCPD dates. This does not check calendar validity; placeholder-only
* strings such as {@code --------} or {@code 00000000} are accepted.
*/
public static boolean hasMcpdDateSyntax(String date) {
if (StringUtils.isBlank(date) || date.length() != 8) {
return false;
}
for (int i = 0; i < date.length(); i++) {
char c = date.charAt(i);
if (c != '-' && (c < '0' || c > '9')) {
return false;
}
}
return true;
}
/**
* Nulls out blank values, values that do not have 8-character digit-or-dash
* syntax, and MCPD dates with a placeholder year ({@code ----} or {@code 0000}).
* A "real" date requires a non-placeholder year and at least one non-zero digit.
*/
public static String cleanMcpdDate(String date) {
if (StringUtils.isBlank(date)) {
return null;
}
date = date.trim();
if (!hasMcpdDateSyntax(date)) {
return null;
}
if (Strings.CI.startsWith("----", date) || Strings.CI.startsWith("0000", date)) {
return null;
}
boolean hasRealValue = false;
for (int i = 0; i < date.length(); i++) {
char c = date.charAt(i);
if (c != '-' && c != '0') {
hasRealValue = true;
break;
}
}
return hasRealValue ? date : null;
}
public static boolean isWiewsCode(String code) {
return code == null ? false : WIEWSCODE_PATTERN.matcher(code).matches();
}
}